Hunting Invisible Unicode In Emails
Query
EmailEvents
| where Timestamp > ago(1d)
| where isnotempty(Subject)
| extend SenderIP = iff(isnotempty(SenderIPv4), SenderIPv4, SenderIPv6)
| extend Country = tostring(geo_info_from_ip_address(SenderIP).country)
| extend SubjectSuspiciousChars = extract_all(@"([\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF])", Subject)
| extend SenderDisplaySuspiciousChars = extract_all(@"([\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF])", SenderDisplayName)
| extend HasEmoji = Subject matches regex @"[\u2600-\u27BF]"
| extend SubjectUnicode = iff(array_length(SubjectSuspiciousChars) > 0, 1, 0), SenderDisplayUnicode = iff(array_length(SenderDisplaySuspiciousChars) > 0, 1, 0)
| summarize Timestamp=max(Timestamp), HasEmoji=max(toint(HasEmoji)), SubjectUnicode=max(SubjectUnicode), SenderDisplayUnicode=max(SenderDisplayUnicode), SubjectSuspiciousChars=make_set(SubjectSuspiciousChars), SenderDisplaySuspiciousChars=make_set(SenderDisplaySuspiciousChars), Countries=make_set(Country), SenderIPs=make_set(SenderIP), SenderDisplayNames=make_set(SenderDisplayName), SenderAddresses=make_set(SenderFromAddress), SenderDomains=make_set(SenderFromDomain), DeliveryLocations=make_set(LatestDeliveryLocation), ThreatTypes=make_set(ThreatTypes), ThreatNames=make_set(ThreatNames), EmailCount=count() by Subject, NetworkMessageId, RecipientEmailAddress
| extend CountryCount = array_length(Countries)
| extend MultiCountry = iff(CountryCount > 1, 1, 0)
| extend RiskScore = (HasEmoji * 40) + (SubjectUnicode * 20) + (SenderDisplayUnicode * 20) + (MultiCountry * 20)
| extend RiskReasons = strcat(iff(HasEmoji == 1, "Unicode symbol in Subject [+40]; ", ""), iff(SubjectUnicode == 1, "Hidden Unicode in Subject [+20]; ", ""), iff(SenderDisplayUnicode == 1, "Hidden Unicode in SenderDisplayName [+20]; ", ""), iff(MultiCountry == 1, strcat("Multiple Countries [+20] (", CountryCount, "); "), ""))
| where RiskScore > 0
| project Timestamp, NetworkMessageId, RecipientEmailAddress, RiskScore, RiskReasons, Subject, SubjectSuspiciousChars, SenderDisplayNames, SenderDisplaySuspiciousChars, Countries, CountryCount, SenderIPs, SenderAddresses, SenderDomains, DeliveryLocations, EmailCount, ThreatTypes, ThreatNames
| order by RiskScore desc, Timestamp descAbout this query
MITRE ATT&CK Technique(s)
| Technique ID | Title |
|---|---|
| T1566 | Phishing |
Author: Sergio Albea (22/09/2026)
Hunting Invisible Unicode in Emails
Description: For years, when hunting phishing emails, I have mainly focused on what the user sees inside of it. But I think AI agents are going to change this. An email may soon have two audiences: Human + AI Agent. And they may not always see exactly the same thing. In this new KQL, I explore a simple example: invisible Unicode characters. Microsoft and Micro<U+200B>soft may look the same to us, but not necessarily to a detection, filter, parser or AI agent. For attackers, this difference can be interesting as a way to obfuscate content and potentially evade keyword-based detections or other systems processing the text.
I hunt these patterns in email and add context around them. Because attackers will not only write emails for us. They will(are) start writing emails for our agents too.
Explanation
This KQL query is designed to detect potentially suspicious emails by examining the presence of invisible Unicode characters and other unusual patterns that might be used to evade detection systems. Here's a simplified breakdown of what the query does:
-
Data Source: It starts by looking at email events from the last day (
Timestamp > ago(1d)). -
Extract Information: It extracts various details from the emails, such as the sender's IP address, country, subject line, and sender display name.
-
Detect Invisible Unicode: The query checks for invisible Unicode characters in the email's subject and sender display name. These characters can be used to obfuscate text, making it look normal to humans but different to machines.
-
Emoji Detection: It also checks if there are any emojis in the subject line.
-
Calculate Risk Score: Each email is given a risk score based on:
- Presence of emojis in the subject (+40 points).
- Presence of invisible Unicode in the subject (+20 points).
- Presence of invisible Unicode in the sender's display name (+20 points).
- Emails originating from multiple countries (+20 points).
-
Summarize and Rank: The query summarizes the data, calculating the maximum risk score and other details for each email. It then ranks the emails by risk score and timestamp.
-
Output: Finally, it projects (selects) relevant fields to display, such as the timestamp, risk score, reasons for the risk score, and other email details, and orders the results by risk score and timestamp.
The goal of this query is to identify emails that might be using sophisticated techniques to bypass traditional detection methods, focusing on how both humans and AI agents might perceive the email content differently.