Punycode lookalikes
Description
Punycode lookalike domains in Emails and Teams messages
Query · kql
let AsciiLookalikeChars = @"([\u0430\u0435\u043E\u0440\u0441\u0445\u0456\u0443\u043C\u043D\u0455\u0442\u04BB\u0501\u03B1\u03B5\u03BF\u03C1\u03C7\u03BA\u03BD\u03F2\uFF41-\uFF5A\uFF21-\uFF3A])";
// Function
let NormalizeLookalikes = (s:string) {
replace(@"\u0501","d",
replace(@"\u04BB","h",
replace(@"\u0442","t",
replace(@"\u0455","s",
replace(@"\u043D","n",
replace(@"\u043C","m",
replace(@"\u0443","y",
replace(@"\u0456","i",
replace(@"\u03C7","x",
replace(@"\u0445","x",
replace(@"\u03F2","c",
replace(@"\u0441","c",
replace(@"\u03C1","p",
replace(@"\u0440","p",
replace(@"\u03BF","o",
replace(@"\u043E","o",
replace(@"\u03B5","e",
replace(@"\u0435","e",
replace(@"\u03B1","a",
replace(@"\u0430","a",s)
)))))))))))))))))))};
// End Function
// Function
let SuspiciousPunycodeDomains = (T:(TimeGenerated:datetime, Url:string, UrlDomain:string, ReportId:string, NetworkMessageId:string, TeamsMessageId:string, Source:string)) {
T
| where UrlDomain has "xn--"
| extend DomainUnicode = punycode_domain_from_string(UrlDomain)
| extend Lookalikes = extract_all(AsciiLookalikeChars, DomainUnicode)
| where array_length(Lookalikes) > 0
| extend Normalized = NormalizeLookalikes(DomainUnicode)
| where Normalized matches regex @"^[A-Za-z0-9\.\-]+$"
| project TimeGenerated, Url, UrlDomain, DomainUnicode, Lookalikes, Normalized, ReportId, NetworkMessageId, TeamsMessageId, Source;
};
// End Function
let EmailFindings =
EmailUrlInfo
| project TimeGenerated, Url, UrlDomain, ReportId, NetworkMessageId, TeamsMessageId = "", Source = "Email"
| invoke SuspiciousPunycodeDomains()
| join kind=innerunique (
EmailEvents
| project NetworkMessageId, EmailTimeGenerated = TimeGenerated, ReportId, RecipientEmailAddress, SenderFromAddress, SenderMailFromAddress, SenderDisplayName, Subject, DeliveryAction, DeliveryLocation, ThreatTypes
) on NetworkMessageId
| project
Timestamp = TimeGenerated,
Source,
TimeGenerated,
ReportId,
NetworkMessageId,
TeamsMessageId,
Url,
UrlDomain,
DomainUnicode,
Lookalikes,
Normalized,
Subject,
ThreatTypes,
DeliveryAction,
DeliveryLocation,
SenderDisplayName,
SenderFromAddress,
SenderMailFromAddress,
RecipientEmailAddress;
let TeamsFindings =
MessageUrlInfo
| project TimeGenerated, Url, UrlDomain, ReportId, NetworkMessageId = "", TeamsMessageId, Source = "Teams"
| invoke SuspiciousPunycodeDomains()
| join kind=innerunique (
MessageEvents
| project TeamsMessageId, MessageTimeGenerated = TimeGenerated, Timestamp, SenderEmailAddress, SenderDisplayName, SenderObjectId, SenderType, RecipientDetails, GroupId, GroupName, ThreadId, ThreadName, ThreadType, IsExternalThread, MessageType, MessageSubtype, Subject, ThreatTypes, DeliveryAction, DeliveryLocation, ReportId
) on TeamsMessageId
| project
Timestamp = TimeGenerated,
Source,
TimeGenerated,
ReportId,
NetworkMessageId,
TeamsMessageId,
Url,
UrlDomain,
DomainUnicode,
Lookalikes,
Normalized,
Subject,
ThreatTypes,
DeliveryAction,
DeliveryLocation,
SenderDisplayName,
SenderEmailAddress,
SenderObjectId,
SenderType,
RecipientDetails,
GroupId,
GroupName,
ThreadId,
ThreadName,
ThreadType,
IsExternalThread,
MessageType,
MessageSubtype
//| where IsExternalThread == true // Use filter if only external threats are in scope
;
union isfuzzy=true EmailFindings, TeamsFindings
| order by TimeGenerated desc