Detect Invisible Unicode in Splunk
Adversaries may abuse invisible or non-printing Unicode characters to conceal malicious content within files, scripts, or text. By inserting characters that do not visibly render, such as zero-width spaces, zero-width joiners, bidirectional control characters or the Hangul filler, adversaries may hide data, alter how content is interpreted, or make malicious code appear as benign text or whitespace. Payloads can be encoded as runs of these characters and decoded at execution time by a script that is itself short and innocuous looking.
MITRE ATT&CK
- Tactic
- Stealth
- Technique
- T1027 Obfuscated Files or Information
- Sub-technique
- T1027.018 Invisible Unicode
- Canonical reference
- https://attack.mitre.org/techniques/T1027/018/
SPL Detection Query
``` df00tech DF00-9f9c08e410 · https://df00tech.com/detections/T1027.018 · licence: https://df00tech.com/detections/licence ```
index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
| rex max_match=0 field=CommandLine "(?<InvisibleChar>[\x{200B}-\x{200F}\x{2060}-\x{2064}\x{202A}-\x{202E}\x{2066}-\x{2069}\x{3164}\x{115F}\x{1160}\x{FEFF}])"
| eval InvisibleCount=mvcount(InvisibleChar)
| where InvisibleCount >= 3
| table _time, host, User, Image, CommandLine, ParentImage, ParentCommandLine, InvisibleCount
| sort - InvisibleCount, - _time Detects process creation events (Sysmon Event ID 1) whose CommandLine contains three or more invisible or bidirectional-control Unicode characters. rex with max_match=0 extracts every match into a multivalue field and mvcount counts them. Sysmon Event ID 1 carries CommandLine, Image, ParentImage and ParentCommandLine.
Data Sources
Required Sourcetypes
False Positives & Tuning
- Command lines copied from chat tools, web pages or rich-text documents that carry stray zero-width spaces
- Right-to-left language content in file names or arguments that legitimately uses directional marks
- Localisation or text-processing tools that pass Unicode test strings on the command line
Other platforms for T1027.018
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1 Zero-width characters in a PowerShell command line
Expected signal: Sysmon Event ID 1: Process Create with Image=powershell.exe and a CommandLine containing five U+200B characters. Security Event ID 4688 (if command line auditing is enabled) with the same details. PowerShell ScriptBlock Log Event ID 4104 with the script text.
- Test 2 Invisible characters inside a script file
Expected signal: PowerShell ScriptBlock Log Event ID 4104 with ScriptBlockText containing U+200D and U+3164. Sysmon Event ID 1 for the child powershell.exe with -File pointing at the script. Sysmon Event ID 11 for the script file creation.
- Test 3 Zero-width characters in a Linux shell command line
Expected signal: Process creation telemetry from auditd EXECVE records, Sysmon for Linux Event ID 1 or an EDR sensor, with an argument containing the UTF-8 bytes E2 80 8B three times.
References (5)
- https://attack.mitre.org/techniques/T1027/018/
- https://www.unicode.org/charts/PDF/U2000.pdf
- https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging_windows
- https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/re2
- https://github.com/redcanaryco/atomic-red-team/tree/master/atomics
Response Playbook
Triage
- Print the command line with the invisible characters made visible (for example escape every code point above U+007F) and identify which characters are present: U+200B-U+200D zero-width, U+2060-U+2064 word joiner and invisible operators, U+202A-U+202E and U+2066-U+2069 bidirectional controls, U+3164 Hangul filler
- Check whether the characters sit inside a string literal, a variable name or between tokens — runs inside a long string usually mean an encoded payload, a lone character inside an argument suggests filename or argument spoofing
- Identify the process and its parent — was it a script host (powershell.exe, wscript.exe, python, bash) started from a document, archive or browser download?
- Check the user and device context — would this user normally run scripts? Do they use right-to-left languages or paste from rich-text sources?
- Find the script file or text that the command line references and look for the same characters in its contents
- Look for follow-on activity from the same process: network connections, child processes, file writes
Containment
- If the decoded payload is malicious: isolate the endpoint using EDR network isolation
- Quarantine the script, document or archive that carried the invisible characters and block its hash
- If a user account ran the payload: reset credentials and revoke active sessions and tokens
- Block the delivery source (sender, URL or domain) at mail gateway and proxy
- Search other endpoints for the same character run or file hash before closing the incident
Evidence Collection
- PowerShell Script Block Logging (Event ID 4104) — the ScriptBlockText field preserves the exact characters executed
- Process Creation Events — Sysmon Event ID 1 or Security Event ID 4688 with command line auditing enabled
- The script or document file itself, collected with a hash, for byte-level inspection of the code points
- File Creation Events — Sysmon Event ID 11 for scripts written by the suspicious process
- Network Connection Events — Sysmon Event ID 3 for connections from the same process
- Mail or proxy logs identifying how the file was delivered
Escalation Criteria
- !Invisible characters decode to executable code or a second-stage payload
- !The same character run appears on multiple endpoints within a short window
- !The process making use of the characters also made outbound connections to non-corporate addresses
- !Execution under a privileged or service account with no change ticket
- !Evidence of other defense evasion alongside it, such as AMSI tampering or disabling of security tools
Investigation Guide
Forensic Artifacts
- >
Event Log: Microsoft-Windows-PowerShell/Operational (Event ID 4104 script block text) - >
Event Log: Microsoft-Windows-Sysmon/Operational (Event ID 1 command lines) - >
File System: $env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt — typed command history - >
File System: the carrier script, document or text file, kept for hex inspection - >
File System: ~/.bash_history and ~/.zsh_history on Linux and macOS hosts
Tuning Guidance
Start by baselining how often invisible characters appear in command lines in your environment. In organisations with right-to-left language users, exclude U+200E, U+200F and U+202A-U+202E by account group or narrow the character class to the zero-width and filler characters (U+200B-U+200D, U+2060, U+3164). Raise the InvisibleCount threshold above 3 for noisy fleets, but never exclude by process name alone, since attackers choose the host process. A lone U+FEFF at the start of an argument is a byte order mark and usually benign. The most reliable second signal is a script host that both carries the characters and makes a network connection or spawns a child process. Enable PowerShell Script Block Logging so the decoded stage can be examined.
Hunting Queries
Hunt for PowerShell script blocks that contain many invisible characters. A script that hides a payload as a long run of zero-width characters usually has to decode it in memory, which Script Block Logging records.
// df00tech DF00-9f9c08e410 · https://df00tech.com/detections/T1027.018 · licence: https://df00tech.com/detections/licence
DeviceEvents
| where Timestamp > ago(7d)
| where ActionType == "PowerShellCommand"
| where AdditionalFields has_any ("\\u200b", "\\u200c", "\\u200d", "\\u2060", "\\ufeff", "\\u3164")
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, InitiatingProcessCommandLine, AdditionalFields
| sort by Timestamp desc ``` df00tech DF00-9f9c08e410 · https://df00tech.com/detections/T1027.018 · licence: https://df00tech.com/detections/licence ```
index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-PowerShell/Operational" EventCode=4104
| rex max_match=0 field=ScriptBlockText "(?<InvisibleChar>[\x{200B}-\x{200D}\x{2060}\x{FEFF}\x{3164}])"
| eval InvisibleCount=mvcount(InvisibleChar)
| where InvisibleCount >= 10
| table _time, host, UserID, InvisibleCount, ScriptBlockText
| sort - InvisibleCount Baseline which accounts and parent processes launch commands containing any invisible character. Low-volume, repeated sources are usually benign copy-paste habits, while a rare parent such as a script host or an Office process warrants review.
// df00tech DF00-9f9c08e410 · https://df00tech.com/detections/T1027.018 · licence: https://df00tech.com/detections/licence
DeviceProcessEvents
| where Timestamp > ago(7d)
| where ProcessCommandLine matches regex @"[\x{200B}-\x{200F}\x{2060}-\x{2064}\x{202A}-\x{202E}\x{2066}-\x{2069}\x{3164}\x{FEFF}]"
| summarize Count=count(), Devices=dcount(DeviceName), Earliest=min(Timestamp), Latest=max(Timestamp) by AccountName, InitiatingProcessFileName
| sort by Count desc ``` df00tech DF00-9f9c08e410 · https://df00tech.com/detections/T1027.018 · licence: https://df00tech.com/detections/licence ```
index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
| regex CommandLine="[\x{200B}-\x{200F}\x{2060}-\x{2064}\x{202A}-\x{202E}\x{2066}-\x{2069}\x{3164}\x{FEFF}]"
| stats count as Count, dc(host) as Devices, earliest(_time) as Earliest, latest(_time) as Latest by User, ParentImage
| sort - Count Atomic Red Team Tests
Runs a benign PowerShell command whose string argument has a run of five zero-width spaces (U+200B) inserted, which is the shape of an invisible-payload marker. The command only writes text to the console.
Command
powershell.exe -NoProfile -Command "Write-Output 'df00techtest'" Expected Telemetry
Sysmon Event ID 1: Process Create with Image=powershell.exe and a CommandLine containing five U+200B characters. Security Event ID 4688 (if command line auditing is enabled) with the same details. PowerShell ScriptBlock Log Event ID 4104 with the script text.
Expected Detection
Alert fires because the command line holds at least three characters from the invisible class. KQL: InvisibleCount=5. SPL: InvisibleCount=5.
Writes a small PowerShell script whose variable value carries zero-width joiners and a Hangul filler, runs it, then deletes it. The characters reach the logs through script block text instead of the process command line.
Command
Set-Content -Path $env:TEMP\df00tech-invisible.ps1 -Value "`$s = 'aㅤㅤb'; Write-Output `$s.Length" -Encoding UTF8; powershell.exe -NoProfile -ExecutionPolicy Bypass -File $env:TEMP\df00tech-invisible.ps1 Cleanup
Remove-Item $env:TEMP\df00tech-invisible.ps1 -ErrorAction SilentlyContinue Expected Telemetry
PowerShell ScriptBlock Log Event ID 4104 with ScriptBlockText containing U+200D and U+3164. Sysmon Event ID 1 for the child powershell.exe with -File pointing at the script. Sysmon Event ID 11 for the script file creation.
Expected Detection
The process rule does not fire because the characters are in the file, not the command line. The 4104 hunting query counts five invisible characters in the script block (lower its threshold to 5 for this test).
Uses command substitution so that the shell passes an argument containing three zero-width spaces to sh. The command only echoes a string.
Command
sh -c "$(printf 'echo df00tech\xe2\x80\x8b\xe2\x80\x8b\xe2\x80\x8btest')" Expected Telemetry
Process creation telemetry from auditd EXECVE records, Sysmon for Linux Event ID 1 or an EDR sensor, with an argument containing the UTF-8 bytes E2 80 8B three times.
Expected Detection
Alert fires on platforms that ingest Linux process command lines into the same fields: the command line holds three characters from the invisible class.