Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions detection-rules/ai_voice_fraud.kql
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// ============================================================
// RetailShield — AI Voice Fraud Detection
// Rule ID : RS-VOI-001
// MITRE ATT&CK : T1598 — Phishing for Information (Voice)
// Tactic : Reconnaissance
// Severity : High
// Frequency : Every 30 minutes | Lookback: 30 minutes
// Author : Tanvir Farhad — ShieldTech Ltd, London
// ============================================================
// Detects AI-generated deepfake voice calls targeting retail
// finance and management staff. Covers high AI confidence score
// calls, urgent financial requests from unverified callers,
// caller ID spoofing patterns, and after-hours vishing attempts.
// Retail-specific: targets CFO/finance-role impersonation and
// urgent payment override requests unique to retail operations.
// ============================================================

let LookbackWindow = 30m;
let BusinessHoursStart = 7; // 07:00 UTC
let BusinessHoursEnd = 20; // 20:00 UTC
let AIConfidenceThreshold = 0.85; // AI voice deepfake confidence above this is high-risk
let HighRiskSuspicionScore = 85;

// Roles that are high-value targets for voice fraud
let SensitiveTargetRoles = dynamic([
"finance", "director", "manager", "cfo", "ceo",
"payment", "accounts", "payroll", "head"
]);

// Impersonation personas used in retail voice fraud
let KnownImpersonationEntities = dynamic([
"Area Manager", "Head Office", "CFO", "CEO",
"Finance Director", "Regional Director", "DHL",
"HMRC", "Bank", "IT Support"
]);

// Keywords that signal urgency / bypass request — fraud indicators
let FraudKeywords = dynamic([
"urgent", "immediate", "emergency", "override",
"bypass", "redirect", "transfer", "wire", "bacs",
"authorise", "approve now", "time sensitive", "confidential"
]);

// ── Signal 1 — High AI confidence score on detected voice call ───────────────
let HighConfidenceVoiceFraud =
RetailShield_Logs_CL
| where ingestion_time() > ago(LookbackWindow)
| where EventType_s == "AI_Voice_Fraud"
| where todouble(AIConfidenceScore_d) >= AIConfidenceThreshold
| project
TimeGenerated,
SignalType = "HighConfidenceVoiceFraud",
TargetEmployee = tostring(TargetEmployee_s),
AIConfidenceScore = AIConfidenceScore_d,
ImpersonatingEntity = tostring(ImpersonatingEntity_s),
RequestMade = tostring(RequestMade_s),
SuspicionScore = todouble(SuspicionScore_d);

// ── Signal 2 — Urgent financial / bypass request on voice channel ────────────
let UrgentFinancialRequest =
RetailShield_Logs_CL
| where ingestion_time() > ago(LookbackWindow)
| where EventType_s == "AI_Voice_Fraud"
| where tolower(tostring(RequestMade_s)) has_any (FraudKeywords)
| where tolower(tostring(TargetEmployee_s)) has_any (SensitiveTargetRoles)
or tostring(ImpersonatingEntity_s) in (KnownImpersonationEntities)
| project
TimeGenerated,
SignalType = "UrgentFinancialRequest",
TargetEmployee = tostring(TargetEmployee_s),
AIConfidenceScore = AIConfidenceScore_d,
ImpersonatingEntity = tostring(ImpersonatingEntity_s),
RequestMade = tostring(RequestMade_s),
SuspicionScore = todouble(SuspicionScore_d);

// ── Signal 3 — Caller ID spoofing pattern detected ───────────────────────────
let SpoofedCallerID =
RetailShield_Logs_CL
| where ingestion_time() > ago(LookbackWindow)
| where EventType_s == "AI_Voice_Fraud"
| where SuspicionScore_d >= HighRiskSuspicionScore
| where isnotempty(CallerID_s)
| where tostring(CallerID_s) matches regex @"^(\+44|0044|0)[0-9]{9,10}$"
| project
TimeGenerated,
SignalType = "SpoofedCallerID",
TargetEmployee = tostring(TargetEmployee_s),
AIConfidenceScore = AIConfidenceScore_d,
ImpersonatingEntity = tostring(ImpersonatingEntity_s),
RequestMade = tostring(RequestMade_s),
SuspicionScore = todouble(SuspicionScore_d);

// ── Signal 4 — After-hours vishing attempt ───────────────────────────────────
let AfterHoursVoiceFraud =
RetailShield_Logs_CL
| where ingestion_time() > ago(LookbackWindow)
| where EventType_s == "AI_Voice_Fraud"
| extend CallHour = hourofday(TimeGenerated)
| where CallHour < BusinessHoursStart or CallHour >= BusinessHoursEnd
| project
TimeGenerated,
SignalType = "AfterHoursVoiceFraud",
TargetEmployee = tostring(TargetEmployee_s),
AIConfidenceScore = AIConfidenceScore_d,
ImpersonatingEntity = tostring(ImpersonatingEntity_s),
RequestMade = tostring(RequestMade_s),
SuspicionScore = todouble(SuspicionScore_d);

// ── Union all signals ─────────────────────────────────────────────────────────
union HighConfidenceVoiceFraud, UrgentFinancialRequest, SpoofedCallerID, AfterHoursVoiceFraud
| summarize
SignalCount = count(),
Signals = make_set(SignalType),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
MaxAIScore = max(todouble(AIConfidenceScore)),
MaxSuspicionScore = max(todouble(SuspicionScore)),
TargetEmployee = take_any(TargetEmployee),
ImpersonatingEntity = take_any(ImpersonatingEntity),
RequestMade = take_any(RequestMade)
by bin(TimeGenerated, LookbackWindow)
| where SignalCount >= 1
| extend
RiskScore = case(SignalCount >= 3, 90, SignalCount == 2, 78, 65),
MitreTechnique = "T1598",
MitreTactic = "Reconnaissance",
AlertSeverity = case(SignalCount >= 3, "CRITICAL", "HIGH"),
Severity = "HIGH",
PlaybookTrigger = "notify_soc",
DeviceName = "VOIP-SYSTEM",
AlertTitle = strcat("AI Voice Fraud Detected — Target: ", TargetEmployee,
" | Impersonating: ", ImpersonatingEntity)
| project
AlertTitle,
DeviceName,
TargetEmployee,
ImpersonatingEntity,
RequestMade,
AlertSeverity,
Severity,
RiskScore,
SignalCount,
Signals,
MaxAIScore,
MaxSuspicionScore,
MitreTechnique,
MitreTactic,
PlaybookTrigger,
FirstSeen,
LastSeen
| sort by RiskScore desc
154 changes: 154 additions & 0 deletions detection-rules/pos_anomaly.kql
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// ============================================================
// RetailShield — POS Anomaly Detection
// Rule ID : RS-POS-001
// MITRE ATT&CK : T1056.001 — Keylogging / RAM Scraping
// Tactic : Collection
// Severity : High
// Frequency : Every 15 minutes | Lookback: 30 minutes
// Author : Tanvir Farhad — ShieldTech Ltd, London
// ============================================================
// Detects RAM-scraping keyloggers and anomalous activity on
// retail POS terminals. Covers unknown DLL injection into POS
// processes, abnormal transaction volume spikes (statistical
// anomaly), process memory dumps, and suspicious outbound
// network connections from POS endpoints to threat IPs.
// ============================================================

let LookbackWindow = 30m;
let BaselineWindow = 30d;
let TransactionVolumeThreshold = 3.5; // std deviations above 30-day baseline

// POS application process names
let POSProcessNames = dynamic([
"xstore.exe", "aloha.exe", "tcxpos.exe",
"posready.exe", "retail.exe", "revel.exe", "squarepos.exe"
]);

// Hostname prefixes that identify POS / till hardware
let RetailTerminalPrefix = dynamic([
"pos-", "till-", "kiosk-", "ped-", "term-"
]);

// Folders that are trusted for DLL loads — flag anything outside
let TrustedDLLPaths = dynamic([
@"C:\Windows\System32",
@"C:\Windows\SysWOW64",
@"C:\Program Files",
@"C:\Program Files (x86)"
]);

// ── Signal 1 — Unknown unsigned DLL injected into POS process ────────────────
let UnknownDLLInjection =
DeviceEvents
| where ingestion_time() > ago(LookbackWindow)
| where ActionType == "ImageLoaded"
| where InitiatingProcessFileName has_any (POSProcessNames)
| where FileName endswith ".dll" or FileName endswith ".sys"
| where isempty(SHA1) or Signer == "" or isempty(Signer)
| where not(FolderPath has_any (TrustedDLLPaths))
| extend DeviceLower = tolower(DeviceName)
| where DeviceLower has_any (RetailTerminalPrefix)
| project
TimeGenerated, DeviceName, DeviceId,
SignalType = "UnknownDLLInjection",
POSProcess = InitiatingProcessFileName,
SuspiciousDLL = FileName,
DLLPath = FolderPath,
SHA256 = InitiatingProcessSHA256;

// ── Signal 2 — Abnormal transaction volume (statistical spike) ───────────────
let AbnormalTransactionVolume =
RetailShield_Logs_CL
| where ingestion_time() > ago(BaselineWindow)
| where EventType_s == "POS_Transaction"
| summarize HourlyCount = count() by TerminalID_s, bin(TimeGenerated, 1h)
| summarize AvgCount = avg(HourlyCount), StdDev = stdev(HourlyCount)
by TerminalID_s
| join kind=inner (
RetailShield_Logs_CL
| where ingestion_time() > ago(LookbackWindow)
| where EventType_s == "POS_Transaction"
| summarize RecentCount = count() by TerminalID_s
) on TerminalID_s
| where RecentCount > AvgCount + (TransactionVolumeThreshold * StdDev)
| extend AnomalyScore = round((RecentCount - AvgCount) / StdDev, 2)
| project
TimeGenerated = now(), DeviceName = TerminalID_s, DeviceId = "",
SignalType = "AbnormalTransactionVolume",
POSProcess = "POS_Transaction",
SuspiciousDLL = "",
DLLPath = "",
SHA256 = "";

// ── Signal 3 — Process memory dump targeting a POS application ───────────────
let ProcessMemoryDump =
DeviceEvents
| where ingestion_time() > ago(LookbackWindow)
| where ActionType in ("ProcessDumped", "MemoryDumpCreated")
or (ActionType == "ProcessAccessed"
and ProcessCommandLine has_any ("MiniDump", "ProcDump", "createdump", "procdump"))
| where tolower(TargetProcessFileName) has_any (POSProcessNames)
| project
TimeGenerated, DeviceName, DeviceId,
SignalType = "ProcessMemoryDump",
POSProcess = TargetProcessFileName,
SuspiciousDLL = InitiatingProcessFileName,
DLLPath = ProcessCommandLine,
SHA256 = InitiatingProcessSHA256;

// ── Signal 4 — Suspicious outbound network from POS to threat IP ─────────────
let SuspiciousNetworkFromPOS =
DeviceNetworkEvents
| where ingestion_time() > ago(LookbackWindow)
| where tolower(DeviceName) has_any (RetailTerminalPrefix)
| where RemoteIPType == "Public"
| where not(RemotePort in (443, 80, 8443, 8080))
| join kind=inner (
_GetWatchlist("RetailIOCWatchlist")
| project ThreatIP = tostring(column_ifexists("SearchKey", ""))
) on $left.RemoteIP == $right.ThreatIP
| project
TimeGenerated, DeviceName, DeviceId,
SignalType = "SuspiciousNetworkFromPOS",
POSProcess = InitiatingProcessFileName,
SuspiciousDLL = RemoteIP,
DLLPath = tostring(RemotePort),
SHA256 = "";

// ── Union all signals ─────────────────────────────────────────────────────────
union UnknownDLLInjection, AbnormalTransactionVolume, ProcessMemoryDump, SuspiciousNetworkFromPOS
| summarize
SignalCount = count(),
Signals = make_set(SignalType),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
SuspiciousDLL = take_any(SuspiciousDLL),
POSProcess = take_any(POSProcess),
DeviceId = take_any(DeviceId)
by DeviceName
| where SignalCount >= 1
| extend
RiskScore = case(SignalCount >= 3, 95, SignalCount == 2, 82, 65),
MitreTechnique = "T1056.001",
MitreTactic = "Collection",
Severity = case(SignalCount >= 2, "HIGH", "HIGH"),
AlertSeverity = case(SignalCount >= 3, "CRITICAL", "HIGH"),
PlaybookTrigger = "suspend_terminal",
AlertTitle = strcat("POS Anomaly — Potential RAM Scraping on ", DeviceName)
| project
AlertTitle,
DeviceName,
DeviceId,
AlertSeverity,
Severity,
RiskScore,
SignalCount,
Signals,
SuspiciousDLL,
POSProcess,
MitreTechnique,
MitreTactic,
PlaybookTrigger,
FirstSeen,
LastSeen
| sort by RiskScore desc
Loading
Loading