Skip to content

Siemphony is in beta and still being built. How a rule earns its badge.

Siemphony’s repertoire

Invisible Unicode code point rebuilt in a PowerShell script block

MITRE's analytic describes a script that looks benign because its payload is written in characters that do not render, and is reconstructed and evaluated at runtime. Script Block Logging is the only log source in the strategy that shows the script text itself, so this rule requires two things in one 4104 block: a literal reference to one of the zero-width, word-joiner, right-to-left-override or byte-order-mark code points, and a dynamic-execution or decode call. Both the 0x200B literal and the backslash-u escape are matched, since both leave the hex value in the text; the U+200B documentation form is deliberately not, as it appears in comments and prose far more often than in an executable payload. What the rule cannot do is measure MITRE's UnicodeDensityThreshold — a script carrying the raw invisible characters rather than naming their code points is invisible to a text match, so this catches the constructor, not the payload. Script Block Logging is off in a default install and in the MS and CIS baselines; without it the PowerShell channel carries no ScriptBlockText at all. UNVERIFIED AS AUTHORED — derived from MITRE ATT&CK DET0920, not hand-written and not tested by its author. Any lab result is on this rule's own page.Full description

The detection

The 4 SIEM queries below are previews produced by Siemphony’s own translator, not pySigma, and none has been executed against a real backend — review before you deploy.

detection.yml

title: Invisible Unicode code point rebuilt in a PowerShell script blockid: 7eb2f807-be72-40db-9998-122dcdc1c296status: experimentaldescription: |  MITRE's analytic describes a script that looks benign because its payload is  written in characters that do not render, and is reconstructed and evaluated at  runtime. Script Block Logging is the only log source in the strategy that shows  the script text itself, so this rule requires two things in one 4104 block: a  literal reference to one of the zero-width, word-joiner, right-to-left-override  or byte-order-mark code points, and a dynamic-execution or decode call. Both the  0x200B literal and the backslash-u escape are matched, since both leave the  hex value in the text; the U+200B documentation form is deliberately not, as it  appears in comments and prose far more often than in an executable payload.  What the rule cannot do is measure MITRE's UnicodeDensityThreshold — a script  carrying the raw invisible characters rather than naming their code points is  invisible to a text match, so this catches the constructor, not the payload.  Script Block Logging is off in a default install and in the MS and CIS  baselines; without it the PowerShell channel carries no ScriptBlockText at all.  UNVERIFIED AS AUTHORED — derived from MITRE ATT&CK DET0920, not hand-written and not tested by its author. Any lab result is on this rule's own page.references:  - https://attack.mitre.org/techniques/T1027/018  - https://attack.mitre.org/detectionstrategies/DET0920author: Siemphony pipeline (machine-derived from MITRE ATT&CK v19.2)date: 2026-08-16tags:  - attack.defense-evasion  - attack.t1027.018logsource:  product: windows  service: powershelldetection:  selection_event:    EventID: 4104  selection_codepoint:    ScriptBlockText|contains:      - '0x200b'      - '0x200c'      - '0x200d'      - '0x2060'      - '0x202e'      - '0xfeff'      - 'u200b'      - 'u200c'      - 'u200d'      - 'u2060'      - 'u202e'      - 'ufeff'  selection_dynamic:    ScriptBlockText|contains:      - 'Invoke-Expression'      - 'IEX('      - 'IEX ('      - 'FromBase64String'      - '[scriptblock]::Create'  condition: selection_event and selection_codepoint and selection_dynamicfalsepositives:  - "Large modules logged as a single script block, where one function strips a byte-order mark or a zero-width space from imported text and an unrelated function decodes base64. Sigma has no proximity operator, so the two halves need not be connected in any way for the rule to fire, and this is the most likely benign hit on a host that runs sizeable PowerShell modules."  - "Text-normalisation, localisation and right-to-left handling code, which names 0x202E and the zero-width joiners on purpose to sanitise or render user-supplied strings correctly."  - "Obfuscation research, security training material and detection-validation suites, which construct zero-width payloads and evaluate them precisely because that is the behaviour being demonstrated."  - "Installer and packaging scripts that embed a base64 blob and clean up encoding artefacts in the same block. MITRE's ExecutionContext knob is the intended tuning point here, but 4104 records the interpreter that logged the block rather than a distinct execution context, so scoping has to be done on the host or the calling user."level: medium

Sentinel · KQL

Run this as a search.

Event| where ((EventID == 4104 and (ScriptBlockText contains "0x200b" or ScriptBlockText contains "0x200c" or ScriptBlockText contains "0x200d" or ScriptBlockText contains "0x2060" or ScriptBlockText contains "0x202e" or ScriptBlockText contains "0xfeff" or ScriptBlockText contains "u200b" or ScriptBlockText contains "u200c" or ScriptBlockText contains "u200d" or ScriptBlockText contains "u2060" or ScriptBlockText contains "u202e" or ScriptBlockText contains "ufeff")) and (ScriptBlockText contains "Invoke-Expression" or ScriptBlockText contains "IEX(" or ScriptBlockText contains "IEX (" or ScriptBlockText contains "FromBase64String" or ScriptBlockText contains "[scriptblock]::Create"))

Splunk · SPL

Run this as a search.

index=* ((EventID="4104" AND (ScriptBlockText="*0x200b*" OR ScriptBlockText="*0x200c*" OR ScriptBlockText="*0x200d*" OR ScriptBlockText="*0x2060*" OR ScriptBlockText="*0x202e*" OR ScriptBlockText="*0xfeff*" OR ScriptBlockText="*u200b*" OR ScriptBlockText="*u200c*" OR ScriptBlockText="*u200d*" OR ScriptBlockText="*u2060*" OR ScriptBlockText="*u202e*" OR ScriptBlockText="*ufeff*")) AND (ScriptBlockText="*Invoke-Expression*" OR ScriptBlockText="*IEX(*" OR ScriptBlockText="*IEX (*" OR ScriptBlockText="*FromBase64String*" OR ScriptBlockText="*[scriptblock]::Create*"))

Elastic · ES|QL

Run this as a search.

FROM logs-*| WHERE ((event.code == 4104 AND (TO_LOWER(powershell.file.script_block_text) LIKE "*0x200b*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*0x200c*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*0x200d*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*0x2060*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*0x202e*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*0xfeff*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*u200b*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*u200c*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*u200d*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*u2060*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*u202e*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*ufeff*")) AND (TO_LOWER(powershell.file.script_block_text) LIKE "*invoke-expression*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*iex(*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*iex (*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*frombase64string*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*[scriptblock]::create*"))

Wazuh · XML rule

Deploy to your manager — this is a rule, not a search.

<group name="sigma,windows,">  <!-- Rule ids must be unique on your manager; 100000+ is the user range. -->  <rule id="100000" level="7">    <!-- Set <if_sid> to the decoder/base rule for windows so this only evaluates relevant events. -->    <field name="EventID" type="pcre2">(?i)^4104$</field>    <field name="ScriptBlockText" type="pcre2">(?i)(0x200b|0x200c|0x200d|0x2060|0x202e|0xfeff|u200b|u200c|u200d|u2060|u202e|ufeff)</field>    <field name="ScriptBlockText" type="pcre2">(?i)(Invoke-Expression|IEX\(|IEX \(|FromBase64String|\[scriptblock\]::Create)</field>    <description>Invisible Unicode code point rebuilt in a PowerShell script block</description>    <mitre>      <id>T1027.018</id>    </mitre>  </rule></group>

Verdicts · reactions · comments

Community

Verdicts from engineers who actually deployed it, and the conversation around it.

Log in to react
Not yet reported on

Nobody has run this in a real environment and said what happened.

Verdicts from engineers who deployed it

0 cast

No verdicts reported yet. The first one is worth more than the tenth — say what you ran it against.

Log in to report a verdict.

Log in to join the discussion.

No comments yet. Someone who deploys this will have something to say about it.