Command Obfuscation via Unicode Modifier Letters
Identifies the presence of Unicode modifier letters in the process command_line. Adversaries sometimes replace ASCII characters with visually similar Unicode modifier letters to evade simple string-based detections.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2025/11/13"
3integration = ["endpoint", "windows", "system", "m365_defender", "sentinel_one_cloud_funnel", "crowdstrike"]
4maturity = "production"
5updated_date = "2026/09/18"
6
7[rule]
8author = ["Elastic"]
9description = """
10Identifies the presence of Unicode modifier letters in the process command_line. Adversaries sometimes replace ASCII
11characters with visually similar Unicode modifier letters to evade simple string-based detections.
12"""
13from = "now-9m"
14index = [
15 "endgame-*",
16 "logs-crowdstrike.fdr*",
17 "logs-endpoint.events.process-*",
18 "logs-m365_defender.event-*",
19 "logs-sentinel_one_cloud_funnel.*",
20 "logs-system.security*",
21 "logs-windows.forwarded*",
22 "logs-windows.sysmon_operational-*",
23 "winlogbeat-*",
24]
25language = "eql"
26license = "Elastic License v2"
27name = "Command Obfuscation via Unicode Modifier Letters"
28references = ["https://www.wietzebeukema.nl/blog/windows-command-line-obfuscation"]
29risk_score = 73
30rule_id = "37148ae6-c6ec-4fe4-88b1-02f40aed93a9"
31severity = "high"
32tags = [
33 "Domain: Endpoint",
34 "OS: Windows",
35 "Use Case: Threat Detection",
36 "Tactic: Defense Evasion",
37 "Data Source: Elastic Endgame",
38 "Resources: Investigation Guide",
39 "Data Source: Elastic Defend",
40 "Data Source: Windows Security Event Logs",
41 "Data Source: Microsoft Defender XDR",
42 "Data Source: Sysmon",
43 "Data Source: SentinelOne",
44 "Data Source: Crowdstrike",
45 "Noise: Medium",
46 "Performance: Normal",
47 "Profile: Recommended",
48 "Threat: Ransomware",
49 "Rule Type: Event Correlation (EQL)",
50 "Platform: Windows",
51]
52timestamp_override = "event.ingested"
53type = "eql"
54
55query = '''
56process where host.os.type == "windows" and event.type == "start" and
57 (
58 process.name : (
59 "reg.exe", "net.exe", "net1.exe", "certutil.exe", "MSHTA.EXE", "msiexec.exe", "bitsadmin.exe", "CertReq.exe",
60 "PrintBrm.exe", "MSBuild.exe", "wuauclt.exe", "curl.exe", "wget.exe", "ssh.exe", "Cmd.Exe", "PowerShell.EXE",
61 "CONHOST.EXE", "wscript.exe", "cscript.exe", "REGSVR32.EXE", "RUNDLL32.EXE", "procdump.exe", "ntdsutil.exe",
62 "diskshadow.exe", "schtasks.exe", "sc.exe", "wmic.exe", "VSSADMIN.EXE", "WBADMIN.EXE", "iCACLS.EXE",
63 "sftp.exe", "scp.exe", "esentutl.exe", "InstallUtil.exe", "wevtutil.exe"
64 ) or
65 ?process.pe.original_file_name in (
66 "reg.exe", "net.exe", "net1.exe", "CertUtil.exe", "MSHTA.EXE", "msiexec.exe", "bitsadmin.exe", "CertReq.exe",
67 "PrintBrm.exe", "MSBuild.exe", "wuauclt.exe", "curl.exe", "wget.exe", "ssh.exe", "Cmd.Exe", "PowerShell.EXE",
68 "CONHOST.EXE", "wscript.exe", "cscript.exe", "REGSVR32.EXE", "RUNDLL32.EXE", "procdump", "ntdsutil.exe",
69 "diskshadow.exe", "schtasks.exe", "sc.exe", "wmic.exe", "VSSADMIN.EXE", "WBADMIN.EXE", "iCACLS.EXE",
70 "sftp.exe", "scp.exe", "esentutl.exe", "InstallUtil.exe", "wevtutil.exe"
71 )
72 ) and
73 process.command_line regex """.*[ʰ-˿ᴬ-ᶻ]+.*"""
74'''
75
76note = """## Triage and analysis
77
78### Investigating Command Obfuscation via Unicode Modifier Letters
79
80#### Possible investigation steps
81
82- What did the raw modifier-letter command hide after ASCII normalization?
83 - Focus: raw `process.command_line` and modifier-letter code points in `U+02B0-U+02FF` or `U+1D2C-U+1D7B`.
84 - Hint: preserve the raw string, view code points, then compare with NFKC or ASCII-folded rendering; visual review can miss modifier letters a Windows utility may parse as ASCII.
85 - Implication: escalate when normalization reveals a behavior-changing verb, flag, URL, path, or target; lower suspicion when characters remain in localized names, package names, or path text and behavior is unchanged.
86
87- What operational intent does the normalized command express?
88 - Focus: normalized `process.command_line`, `process.name`, and the hidden verb, option, URL, path, or target after ASCII normalization.
89 - Hint: map the hidden token to the utility family: "urlcache" or remote retrieval for certutil/bitsadmin/curl/wget, "encodedcommand" or script execution for PowerShell/cmd/script hosts, add/create/delete for reg/sc/schtasks, shadow/log deletion for vssadmin/wevtutil, and dump/export for procdump/ntdsutil. Treat paired quote insertion or shorthand options as adjacent obfuscation on the same decision path.
90 - Implication: escalate when the normalized token enables high-risk utility behavior; lower suspicion when it remains read-only status, inventory, or installer activity fitting the same process context.
91
92- Is the utility identity consistent with the normalized behavior?
93 - Focus: `process.executable`, `process.pe.original_file_name`, `process.hash.sha256`, `process.code_signature.subject_name`, and `process.code_signature.trusted`.
94 - Implication: escalate when the binary is renamed, user-writable, unsigned/untrusted, mismatched to its original file name, or new for the host; lower suspicion when a signed, stable utility path fits the normalized behavior. Identity alone never clears obfuscation.
95
96- Does the launch and session context explain why this utility received obfuscated text?
97 - Focus: `process.parent.executable`, `process.parent.command_line`, `process.Ext.session_info.logon_type`, `process.Ext.token.elevation_level`, and the `user.id` / `host.id` cohort.
98 - Implication: escalate when a document, browser, archive tool, script host, unexpected interactive user, remote session, or unexplained service chain introduces the command; lower suspicion when a recognized management, installer, or testing launcher runs the same unchanged pattern under the expected account and host cohort.
99
100- Did the obfuscated process launch follow-on process activity?
101 - Focus: child process starts on the same `host.id` where `process.parent.entity_id` matches alert `process.entity_id`; read child `process.name`, `process.executable`, and `process.command_line`. $investigate_0
102 - Hint: inspect same-process file, network, or registry activity for utility effects without child processes. $investigate_1 Missing network telemetry is unresolved, not benign. If `process.entity_id` is absent, pivot with `host.id`, alert `process.pid`, child `process.parent.pid`, and a tight alert window.
103 - Implication: escalate when child activity shows shells, script hosts, installers, remote clients, credential tools, service/task utilities, or cleanup commands matching normalized intent; lower suspicion when no suspicious child follows and the normalized command fits the local workflow.
104
105- If local evidence is suspicious or unresolved, does related alert history change scope?
106 - Focus: related alerts for the same `host.id`, using the strongest local suspicious anchor: normalized command fragment, modifier-letter sequence, utility identity, or parent launcher. $investigate_2
107 - Hint: if host history does not explain the activity, compare related alerts for the same `user.id` with the same anchors. $investigate_3
108 - Implication: broaden containment when the same obfuscation pattern appears across unrelated hosts or users; keep local when the pattern stays confined to one confirmed workflow and process evidence has no contradiction.
109
110- Based on command meaning, utility identity, lineage/session, follow-on processes, and scope, what disposition is supported?
111 - Implication: escalate when normalization changes behavior and identity, lineage, child-process, or scope evidence supports abuse; close only when normalized meaning is unchanged or clearly benign and every process-context category fits one exact workflow; preserve raw command evidence and escalate when evidence is mixed or incomplete.
112
113### False positive analysis
114
115- Localized product names, internationalized paths, user-supplied file names, vendor installers, deployment frameworks, or internal admin tools can introduce non-ASCII package names or paths. Confirm only when normalization leaves behavior unchanged, modifier letters sit in content rather than a verb or flag, utility identity and parent command line match one recognized workflow, and `user.id` plus `host.id` fit the same local task. If records exist, use them as corroboration; otherwise require prior alerts from this rule with the same normalized command pattern, launcher, host, and user.
116- Security testing or detection-validation exercises may intentionally use obfuscated commands. Confirm by matching normalized `process.command_line`, modifier-letter sequence, `process.parent.executable`, `host.id`, and `user.id` to the test scope; outside test records can corroborate but should not override contradictory process evidence.
117- Before creating an exception, build it from the minimum confirmed pattern: normalized `process.command_line`, modifier-letter sequence or code-point range, utility identity in `process.executable` or `process.pe.original_file_name`, launcher context in `process.parent.executable`, and the stable `host.id` or `user.id` cohort. Avoid exceptions on `process.name` alone or the mere presence of non-ASCII characters.
118
119### Response and remediation
120
121- If confirmed benign, reverse any temporary containment and record the raw and normalized `process.command_line`, recovered code points, utility identity, parent command line, and `user.id` / `host.id` evidence that validated the workflow. Create an exception only for the same recurring pattern.
122- If suspicious but unconfirmed, preserve the raw command string, normalized rendering, recovered code points, alerting and child `process.entity_id` values, parent command line, and `user.id` / `host.id` scope before containment. Apply reversible containment first, such as heightened monitoring, temporary account restrictions, or host isolation only when the normalized command, lineage, or follow-on process activity suggests active compromise and the host can tolerate isolation.
123- If confirmed malicious, isolate the host or restrict the affected account based on the normalized intent, launcher chain, child process activity, and related-alert scope. Record alerting and child `process.entity_id` values before suspending or terminating processes, then eradicate only the payloads, configuration changes, or destructive actions identified in the case evidence.
124- Post-incident hardening: replace scripts that require modifier-letter arguments, pin administrative automation to stable signed launcher paths and expected parent command lines, and retain full process command-line, parentage, child-process, and user-host telemetry for future alerts.
125"""
126
127setup = """## Setup
128
129This rule is designed for data generated by [Elastic Defend](https://www.elastic.co/security/endpoint-security), which provides native endpoint detection and response, along with event enrichments designed to work with our detection rules.
130
131Setup instructions: https://ela.st/install-elastic-defend
132
133### Additional data sources
134
135This rule also supports the following third-party data sources. For setup instructions, refer to the links below:
136
137- [CrowdStrike](https://ela.st/crowdstrike-integration)
138- [Microsoft Defender XDR](https://ela.st/m365-defender)
139- [SentinelOne Cloud Funnel](https://ela.st/sentinel-one-cloud-funnel)
140- [Sysmon Event ID 1 - Process Creation](https://ela.st/sysmon-event-1-setup)
141- [Windows Process Creation Logs](https://ela.st/audit-process-creation)
142"""
143
144[rule.investigation_fields]
145field_names = [
146 "@timestamp",
147 "host.id",
148 "user.id",
149 "process.entity_id",
150 "process.pid",
151 "process.executable",
152 "process.command_line",
153 "process.Ext.session_info.logon_type",
154 "process.Ext.token.elevation_level",
155 "process.hash.sha256",
156 "process.pe.original_file_name",
157 "process.code_signature.subject_name",
158 "process.code_signature.trusted",
159 "process.parent.executable",
160 "process.parent.command_line",
161]
162
163[transform]
164
165[[transform.investigate]]
166label = "Child process activity from the obfuscated process"
167description = ""
168providers = [
169 [
170 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
171 { excluded = false, field = "process.parent.entity_id", queryType = "phrase", value = "{{process.entity_id}}", valueType = "string" },
172 { excluded = false, field = "event.category", queryType = "phrase", value = "process", valueType = "string" },
173 { excluded = false, field = "event.type", queryType = "phrase", value = "start", valueType = "string" }
174 ]
175]
176relativeFrom = "now-1h"
177relativeTo = "now"
178
179[[transform.investigate]]
180label = "File, network, or registry activity by the obfuscated process"
181description = ""
182providers = [
183 [
184 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
185 { excluded = false, field = "process.entity_id", queryType = "phrase", value = "{{process.entity_id}}", valueType = "string" },
186 { excluded = false, field = "event.category", queryType = "phrase", value = "file", valueType = "string" }
187 ],
188 [
189 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
190 { excluded = false, field = "process.entity_id", queryType = "phrase", value = "{{process.entity_id}}", valueType = "string" },
191 { excluded = false, field = "event.category", queryType = "phrase", value = "network", valueType = "string" }
192 ],
193 [
194 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
195 { excluded = false, field = "process.entity_id", queryType = "phrase", value = "{{process.entity_id}}", valueType = "string" },
196 { excluded = false, field = "event.category", queryType = "phrase", value = "registry", valueType = "string" }
197 ]
198]
199relativeFrom = "now-1h"
200relativeTo = "now"
201
202[[transform.investigate]]
203label = "Alerts associated with the host"
204description = ""
205providers = [
206 [
207 { excluded = false, field = "event.kind", queryType = "phrase", value = "signal", valueType = "string" },
208 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" }
209 ]
210]
211relativeFrom = "now-48h/h"
212relativeTo = "now"
213
214[[transform.investigate]]
215label = "Alerts associated with the user"
216description = ""
217providers = [
218 [
219 { excluded = false, field = "event.kind", queryType = "phrase", value = "signal", valueType = "string" },
220 { excluded = false, field = "user.id", queryType = "phrase", value = "{{user.id}}", valueType = "string" }
221 ]
222]
223relativeFrom = "now-48h/h"
224relativeTo = "now"
225
226[[rule.threat]]
227framework = "MITRE ATT&CK"
228[[rule.threat.technique]]
229id = "T1027"
230name = "Obfuscated Files or Information"
231reference = "https://attack.mitre.org/techniques/T1027/"
232
233[[rule.threat.technique.subtechnique]]
234id = "T1027.010"
235name = "Command Obfuscation"
236reference = "https://attack.mitre.org/techniques/T1027/010/"
237
238[rule.threat.tactic]
239id = "TA0005"
240name = "Defense Evasion"
241reference = "https://attack.mitre.org/tactics/TA0005/"
Triage and analysis
Investigating Command Obfuscation via Unicode Modifier Letters
Possible investigation steps
-
What did the raw modifier-letter command hide after ASCII normalization?
- Focus: raw
process.command_lineand modifier-letter code points inU+02B0-U+02FForU+1D2C-U+1D7B. - Hint: preserve the raw string, view code points, then compare with NFKC or ASCII-folded rendering; visual review can miss modifier letters a Windows utility may parse as ASCII.
- Implication: escalate when normalization reveals a behavior-changing verb, flag, URL, path, or target; lower suspicion when characters remain in localized names, package names, or path text and behavior is unchanged.
- Focus: raw
-
What operational intent does the normalized command express?
- Focus: normalized
process.command_line,process.name, and the hidden verb, option, URL, path, or target after ASCII normalization. - Hint: map the hidden token to the utility family: "urlcache" or remote retrieval for certutil/bitsadmin/curl/wget, "encodedcommand" or script execution for PowerShell/cmd/script hosts, add/create/delete for reg/sc/schtasks, shadow/log deletion for vssadmin/wevtutil, and dump/export for procdump/ntdsutil. Treat paired quote insertion or shorthand options as adjacent obfuscation on the same decision path.
- Implication: escalate when the normalized token enables high-risk utility behavior; lower suspicion when it remains read-only status, inventory, or installer activity fitting the same process context.
- Focus: normalized
-
Is the utility identity consistent with the normalized behavior?
- Focus:
process.executable,process.pe.original_file_name,process.hash.sha256,process.code_signature.subject_name, andprocess.code_signature.trusted. - Implication: escalate when the binary is renamed, user-writable, unsigned/untrusted, mismatched to its original file name, or new for the host; lower suspicion when a signed, stable utility path fits the normalized behavior. Identity alone never clears obfuscation.
- Focus:
-
Does the launch and session context explain why this utility received obfuscated text?
- Focus:
process.parent.executable,process.parent.command_line,process.Ext.session_info.logon_type,process.Ext.token.elevation_level, and theuser.id/host.idcohort. - Implication: escalate when a document, browser, archive tool, script host, unexpected interactive user, remote session, or unexplained service chain introduces the command; lower suspicion when a recognized management, installer, or testing launcher runs the same unchanged pattern under the expected account and host cohort.
- Focus:
-
Did the obfuscated process launch follow-on process activity?
- Focus: child process starts on the same
host.idwhereprocess.parent.entity_idmatches alertprocess.entity_id; read childprocess.name,process.executable, andprocess.command_line. $investigate_0 - Hint: inspect same-process file, network, or registry activity for utility effects without child processes. $investigate_1 Missing network telemetry is unresolved, not benign. If
process.entity_idis absent, pivot withhost.id, alertprocess.pid, childprocess.parent.pid, and a tight alert window. - Implication: escalate when child activity shows shells, script hosts, installers, remote clients, credential tools, service/task utilities, or cleanup commands matching normalized intent; lower suspicion when no suspicious child follows and the normalized command fits the local workflow.
- Focus: child process starts on the same
-
If local evidence is suspicious or unresolved, does related alert history change scope?
- Focus: related alerts for the same
host.id, using the strongest local suspicious anchor: normalized command fragment, modifier-letter sequence, utility identity, or parent launcher. $investigate_2 - Hint: if host history does not explain the activity, compare related alerts for the same
user.idwith the same anchors. $investigate_3 - Implication: broaden containment when the same obfuscation pattern appears across unrelated hosts or users; keep local when the pattern stays confined to one confirmed workflow and process evidence has no contradiction.
- Focus: related alerts for the same
-
Based on command meaning, utility identity, lineage/session, follow-on processes, and scope, what disposition is supported?
- Implication: escalate when normalization changes behavior and identity, lineage, child-process, or scope evidence supports abuse; close only when normalized meaning is unchanged or clearly benign and every process-context category fits one exact workflow; preserve raw command evidence and escalate when evidence is mixed or incomplete.
False positive analysis
- Localized product names, internationalized paths, user-supplied file names, vendor installers, deployment frameworks, or internal admin tools can introduce non-ASCII package names or paths. Confirm only when normalization leaves behavior unchanged, modifier letters sit in content rather than a verb or flag, utility identity and parent command line match one recognized workflow, and
user.idplushost.idfit the same local task. If records exist, use them as corroboration; otherwise require prior alerts from this rule with the same normalized command pattern, launcher, host, and user. - Security testing or detection-validation exercises may intentionally use obfuscated commands. Confirm by matching normalized
process.command_line, modifier-letter sequence,process.parent.executable,host.id, anduser.idto the test scope; outside test records can corroborate but should not override contradictory process evidence. - Before creating an exception, build it from the minimum confirmed pattern: normalized
process.command_line, modifier-letter sequence or code-point range, utility identity inprocess.executableorprocess.pe.original_file_name, launcher context inprocess.parent.executable, and the stablehost.idoruser.idcohort. Avoid exceptions onprocess.namealone or the mere presence of non-ASCII characters.
Response and remediation
- If confirmed benign, reverse any temporary containment and record the raw and normalized
process.command_line, recovered code points, utility identity, parent command line, anduser.id/host.idevidence that validated the workflow. Create an exception only for the same recurring pattern. - If suspicious but unconfirmed, preserve the raw command string, normalized rendering, recovered code points, alerting and child
process.entity_idvalues, parent command line, anduser.id/host.idscope before containment. Apply reversible containment first, such as heightened monitoring, temporary account restrictions, or host isolation only when the normalized command, lineage, or follow-on process activity suggests active compromise and the host can tolerate isolation. - If confirmed malicious, isolate the host or restrict the affected account based on the normalized intent, launcher chain, child process activity, and related-alert scope. Record alerting and child
process.entity_idvalues before suspending or terminating processes, then eradicate only the payloads, configuration changes, or destructive actions identified in the case evidence. - Post-incident hardening: replace scripts that require modifier-letter arguments, pin administrative automation to stable signed launcher paths and expected parent command lines, and retain full process command-line, parentage, child-process, and user-host telemetry for future alerts.
References
Related rules
- Control Panel Process with Unusual Arguments
- ImageLoad via Windows Update Auto Update Client
- NTDS or SAM Database File Copied
- Potential Local NTLM Relay via HTTP
- Potential Remote Install via MsiExec