T1111

Multi-Factor Authentication Interception

Credential Access Last updated:

Adversaries may target multi-factor authentication (MFA) mechanisms to intercept authentication factors including smart card PINs, hardware token codes (RSA SecurID), SMS-based one-time passwords, and app-based push notifications. Interception methods include keylogging to capture smart card PINs or TOTP codes, SMS hijacking via SIM swapping or compromised messaging service providers, MFA prompt bombing (fatigue attacks sending repeated push notifications until the user approves), and adversary-in-the-middle (AiTM) phishing frameworks that relay credentials and capture session tokens post-MFA. Nation-state groups including Kimsuky (proprietary OTP interception tool), APT42 (cloned websites capturing MFA tokens), and Chimera (registering adversary phone numbers on compromised accounts) have employed these techniques. Criminal group LAPSUS$ operationalized MFA fatigue at scale against major technology firms, achieving access by sending repeated Authenticator push notifications until users approved out of confusion or frustration.

What is T1111 Multi-Factor Authentication Interception?

Multi-Factor Authentication Interception (T1111) maps to the Credential Access tactic — the adversary is trying to steal account names and passwords in MITRE ATT&CK.

This page provides production-ready detection logic for Multi-Factor Authentication Interception, covering the data sources and telemetry it touches: Authentication: Authentication, Logon Session: Logon Session Creation, Azure AD Sign-In Logs, Microsoft Sentinel AADSignInLogs. The queries below are rated high severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.

MITRE ATT&CK

Tactic
Credential Access
Technique
T1111 Multi-Factor Authentication Interception
Canonical reference
https://attack.mitre.org/techniques/T1111/
Microsoft Sentinel / Defender
kusto
// Detection: MFA Fatigue / Prompt Bombing — multiple failed MFA prompts followed by success
let MfaFatigueWindow = 30min;
let MfaPromptThreshold = 5;
let FailedMfaEvents = AADSignInLogs
| where TimeGenerated > ago(24h)
| where ResultType != "0"
| where AuthenticationRequirement == "multiFactorAuthentication"
| where AuthenticationDetails has_any ("MFA", "PhoneAppNotification", "PhoneAppOTP", "OneWaySMS", "TwoWayVoiceMobile")
| project FailTime=TimeGenerated, UserPrincipalName, FailIP=IPAddress, FailLocation=Location;
let SuccessfulMfaEvents = AADSignInLogs
| where TimeGenerated > ago(24h)
| where ResultType == "0"
| where AuthenticationRequirement == "multiFactorAuthentication"
| project SuccessTime=TimeGenerated, UserPrincipalName, SuccessIP=IPAddress, AppDisplayName, SuccessLocation=Location, UserAgent;
FailedMfaEvents
| join kind=inner SuccessfulMfaEvents on UserPrincipalName
| where SuccessTime between (FailTime .. (FailTime + MfaFatigueWindow))
| summarize
    FailCount = dcount(FailTime),
    FirstFailTime = min(FailTime),
    SuccessTime = max(SuccessTime),
    FailSourceIPs = make_set(FailIP),
    SuccessSourceIPs = make_set(SuccessIP),
    TargetApps = make_set(AppDisplayName),
    FailLocations = make_set(FailLocation)
    by UserPrincipalName
| where FailCount >= MfaPromptThreshold
| extend TimeDeltaMinutes = datetime_diff('minute', SuccessTime, FirstFailTime)
| extend AlertType = "MFA Fatigue Attack"
| extend IPMismatch = set_difference(SuccessSourceIPs, FailSourceIPs) != dynamic([])
| project AlertType, UserPrincipalName, FailCount, TimeDeltaMinutes, FirstFailTime, SuccessTime,
         FailSourceIPs, SuccessSourceIPs, IPMismatch, TargetApps, FailLocations
| sort by FailCount desc

Detects MFA fatigue (prompt bombing) attacks using Azure AD Sign-In Logs (AADSignInLogs). Identifies accounts with 5 or more failed MFA authentication attempts within a 30-minute window that are followed by a successful MFA authentication — a pattern consistent with LAPSUS$-style MFA harassment attacks. The IPMismatch field flags cases where the successful authentication came from a different IP than the failed attempts, which may indicate AiTM relay infrastructure. Requires Azure AD P1/P2 or Microsoft Entra ID sign-in log ingestion into Sentinel.

high severity medium confidence

Data Sources

Authentication: Authentication Logon Session: Logon Session Creation Azure AD Sign-In Logs Microsoft Sentinel AADSignInLogs

Required Tables

AADSignInLogs

False Positives

  • Users with poor mobile connectivity who retry MFA push notifications multiple times due to notification delivery failures — particularly common in low-signal areas or when VPN is in use on the authenticator device
  • Users who habitually dismiss MFA notifications accidentally before accepting them, especially with Microsoft Authenticator number matching where dismissal is a single tap away from approval
  • Automated testing frameworks or CI/CD pipelines in non-production tenants that trigger interactive authentication flows repeatedly during integration tests
  • Users traveling across Conditional Access geographic zones triggering multiple re-authentication challenges in rapid succession during transit
  • Help desk password reset workflows where multiple MFA verification rounds occur during account recovery procedures

Sigma rule & cross-platform mapping

The detection logic for Multi-Factor Authentication Interception (T1111) above is provided in a vendor-neutral form so you can deploy it on any SIEM. The same logic is shipped here as native KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the following logsource:

logsource:
  product: azure

Browse the community-maintained Sigma rules for this technique:


Testing Methodology

Validate this detection against 4 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.

  1. Test 1MFA Fatigue Simulation via Repeated MSAL Authentication Requests

    Expected signal: Azure AD Sign-In Logs (AADSignInLogs): 10 entries for the test account — each with ResultType indicating MFA prompt sent or denied, AuthenticationRequirement=multiFactorAuthentication, AuthenticationMethodsUsed=PhoneAppNotification. Events appear within a 3-5 minute window, all from the same source IP (the test machine). If any prompt is approved, a success event (ResultType=0) also appears.

  2. Test 2Smart Card API Enumeration via Custom Process

    Expected signal: Sysmon Event ID 1 (Process Create): scard_probe.exe spawned from powershell.exe with the compilation command in ParentCommandLine. Sysmon Event ID 7 (Image Load): scard_probe.exe loading C:\Windows\System32\winscard.dll — InitiatingProcessFileName=scard_probe.exe is not in the allowlist of expected winscard.dll callers. Sysmon Event ID 11 (File Create): scard_probe.exe written to %TEMP%.

  3. Test 3OTP Keylogger via Low-Level Keyboard Hook Installation

    Expected signal: Sysmon Event ID 1 (Process Create): PowerShell with command line containing SetWindowsHookEx, WH_KEYBOARD_LL references — triggers on process create. Windows Security Event ID 4688 (if command-line audit enabled). PowerShell ScriptBlock Log Event ID 4104 captures the full hook installation code. Behavior-based EDR (CrowdStrike, Defender, SentinelOne) should generate a keyboard hook behavioral alert for the WH_KEYBOARD_LL hook type.

  4. Test 4Adversary MFA Phone Number Registration via Microsoft Graph API

    Expected signal: Azure AD Audit Logs (AuditLogs table in Sentinel / azure:aad:audit in Splunk): OperationName='List user authentication methods' — the enumeration creates an audit event. If the write command is executed (in authorized lab only): OperationName='User registered security info' or 'Add user StrongAuthenticationMethod' with the new phone number value in TargetResources[0].modifiedProperties. Both events include the actor's IP address and UPN.


Response Playbook

Triage

  1. Confirm user identity out-of-band — call the affected user directly (do NOT use email, which may be compromised) and ask if they received unexpected MFA prompts; capture their response as evidence
  2. Review all source IP addresses from the alert in AADSignInLogs — query the IPAddress field against threat intelligence feeds (VirusTotal, AbuseIPDB) and check if IPs match known VPN exit nodes, TOR, or commercial proxy services
  3. Check for IP mismatch between failed attempts and successful authentication (IPMismatch=true in KQL output) — a different IP for the approval may indicate AiTM relay infrastructure proxying the authentication on behalf of the adversary
  4. Examine AuthenticationMethodsUsed in the successful sign-in — if failed attempts used push notifications but success used a weaker method (SMS, voice), this may indicate Conditional Access policy exploitation or MFA method downgrade attack
  5. Identify the application targeted in AppDisplayName — high-value targets (Azure Portal, VPN, GitHub, AWS Console, email) require immediate escalation regardless of user confirmation
  6. Check RiskLevelDuringSignIn and RiskLevelAggregated in AADSignInLogs — if Microsoft Identity Protection already flagged this session as medium/high risk, treat the account as compromised
  7. Look for concurrent Conditional Access policy bypass indicators: check if any Named Location exclusions or Trusted IP range exemptions were applied that allowed the authentication to complete despite risk signals

Containment

  1. Immediately revoke all active sessions for the affected user: in Entra ID admin portal navigate to Users > [User] > Revoke sessions, or run: Revoke-MgUserSignInSession -UserId [UPN] via Microsoft Graph PowerShell
  2. Force a password reset requiring the user to set a new password on next sign-in — ensures any replayed credentials are invalidated even if the session tokens are revoked
  3. Require re-enrollment of MFA authenticator device: in Entra ID > Users > [User] > Authentication methods, delete all existing phone and authenticator app registrations to force re-registration from a trusted device
  4. If SMS-based MFA was the intercepted factor: immediately disable SMS as an allowed MFA method for the account and tenant if possible — migrate to FIDO2 hardware key or Authenticator app with number matching enabled
  5. Block suspicious source IPs at the Conditional Access named locations policy and at the perimeter firewall if IPs are attributable to adversary infrastructure
  6. If smart card proxy authentication suspected (Sykipot-style): revoke the user's certificate at the issuing CA, disable the smart card account in Active Directory, and require new card issuance through physical identity verification
  7. If the targeted application was accessed: audit all actions taken within that application during and after the authentication window — check for data access, privilege escalation, configuration changes, or new OAuth app grants

Evidence Collection

  1. Azure AD Sign-In Logs — export all authentication events for ±72 hours around the incident including ResultType, AuthenticationDetails, ConditionalAccessPolicies, UserAgent, DeviceDetail, and NetworkLocationDetails fields
  2. Azure AD Audit Logs (AuditLogs) — query for 'User registered security info', 'Update user', 'Add user StrongAuthenticationMethod', and 'Delete user StrongAuthenticationMethod' operations in the 7 days preceding the alert; adversary may have pre-registered a phone number
  3. Microsoft Entra ID Protection risk events — export UserRiskEvents and SignInRiskEvents for the affected account; check for unfamiliar sign-in properties, anonymous IP, impossible travel, and leaked credential detections
  4. Conditional Access evaluation logs — export the CAE (Continuous Access Evaluation) events showing which policies evaluated and whether any policy exemptions were applied
  5. Application activity logs — if the targeted app was Azure Portal, export Activity Log from Azure Monitor; if Microsoft 365, export Unified Audit Log via compliance.microsoft.com for the time window
  6. Endpoint telemetry (if keylogger suspected) — collect Sysmon Event ID 7 (ImageLoad) logs for user32.dll, winscard.dll from the affected user's device; capture DeviceProcessEvents for processes with SetWindowsHookEx-related command lines
  7. Email delivery logs — if OTP was sent via email, check Exchange/M365 message trace for OTP delivery; audit Inbox rules for forwarding rules created on the compromised account
  8. Mobile device enrollment records — query Intune enrollment logs and Authenticator app registration timestamps to verify whether any new devices were registered in the days before the attack

Escalation Criteria

  • ! Successful MFA authentication achieved after 5+ failed prompts — treat the account as compromised regardless of user confirmation, as social engineering or distracted approval cannot be ruled out
  • ! IPMismatch between failed and successful authentication IPs, particularly if the success IP resolves to a hosting provider, VPN, or proxy service
  • ! MFA registration changes (new phone number, authenticator app, or OATH token added) within 7 days before the attack — strongly suggests pre-positioning for OTP interception
  • ! Multiple users in the same tenant targeted within 48 hours — indicates a coordinated campaign (LAPSUS$-style) rather than an isolated incident, requiring tenant-wide response
  • ! The targeted application grants access to privileged resources: Azure subscription management, Active Directory administration, cloud provider consoles, VPN infrastructure, or source code repositories
  • ! Concurrent Microsoft Defender for Identity alert for the same user indicating lateral movement, pass-the-hash, or Kerberos attacks — MFA bypass may be part of a broader intrusion chain

Investigation Guide

Forensic Artifacts

  • > Azure AD Sign-In Logs (AADSignInLogs): AuthenticationDetails array — includes per-step MFA method, result, and timestamp; AuthenticationRequirement; ConditionalAccessStatus; DeviceDetail.isCompliant indicating managed vs unmanaged device
  • > Azure AD Audit Logs (AuditLogs): OperationName='User registered security info' and 'Add user StrongAuthenticationMethod' — records phone number or authenticator app additions with actor IP and timestamp
  • > Windows Security Event ID 4768 (Kerberos Authentication Service Request): PreAuthType field — value 16 indicates PKINIT (smart card or certificate-based authentication); CertIssuerName and CertSerialNumber fields for smart card certificate details
  • > Windows Security Event ID 4624 (Successful Logon): LogonType=11 (CachedInteractive) may indicate smart card credential cached use; AuthenticationPackageName=Kerberos with elevated logon for certificate-based auth
  • > Sysmon Event ID 7 (Image Load): Processes loading winscard.dll (C:\Windows\System32\winscard.dll) — legitimate callers are lsass.exe, svchost.exe (SCardSvr), LogonUI.exe, credentialuibroker.exe, and specific vendor smart card middleware
  • > Windows Event Log Microsoft-Windows-SmartCard-Audit/Authentication: Smart card authentication success and failure events including card serial number, reader name, and user principal name
  • > Registry: HKLM\SOFTWARE\Microsoft\Cryptography\Calais\SmartCards — registered smart card readers and minidriver DLL paths; unexpected entries may indicate rogue card readers or spoofed middleware
  • > Browser history and network logs: AiTM phishing domains typically use lookalike URLs (e.g., login.microsoft.com.attacker.com) — check Sysmon Event ID 22 (DNS Query) logs for suspicious domain lookups proxying legitimate auth endpoints
  • > Email and SMS provider delivery logs: SMTP traces for OTP delivery to confirm the OTP was sent to the registered address; check for email forwarding rules (Inbox rules with ForwardTo action) created on the compromised account
  • > Microsoft Entra ID Protection: UserRiskEvents and SignInRiskEvents tables in Sentinel — contains anomalous token detection, anonymous IP, impossible travel, and unfamiliar sign-in property detections that correlate with MFA interception activity

Tuning Guidance

MFA interception detections are inherently noisy because legitimate MFA failures are common in enterprise environments — poor connectivity, missed push notifications, and time synchronization issues on hardware tokens all generate false positives. Begin by establishing a per-user baseline of typical daily failed MFA count using 30-90 days of historical AADSignInLogs data. Most users average fewer than 2 failures per day. Set the fatigue detection threshold at 5 or more failures within 30 minutes as a starting point, then adjust based on your organization's noise level. Consider weighting by application sensitivity: even 3 failed prompts before success on a cloud admin console should trigger review, while 8 failures on a low-risk web app may not warrant escalation. Enable Microsoft Authenticator Number Matching and Additional Context immediately — this single control dramatically reduces both false positives from accidental approvals and the effectiveness of fatigue attacks by requiring explicit code entry. For smart card environments, maintain an allowlist of processes legitimately calling winscard.dll and audit it quarterly as vendor software changes. Create dedicated exclusions for CI/CD service accounts, managed identities, and automation service principals that authenticate repeatedly as part of normal operations — but never suppress by pattern, only by exact account or application ID. If your environment uses Okta or Duo instead of Azure AD MFA, adapt the SPL query to use sourcetype=okta:im:log or sourcetype=duo:auth and equivalent field names (factor, result, reason). For advanced coverage, correlate MFA alert output with Microsoft Defender for Cloud Apps anomaly detection policies that flag impossible travel and activity from anonymous IP ranges.


Hunting Queries

Hunt for accounts where MFA registration methods were modified shortly before risky sign-in events. Chimera and APT42 have been documented registering adversary-controlled phone numbers or authenticator apps on compromised accounts before initiating the actual MFA interception. This query correlates AuditLogs MFA registration operations with Entra ID Protection risky sign-ins by the same user within a 7-day window.

Hunting — KQL
kql
// Hunt for MFA registration changes followed by risky authentication events
let LookbackDays = 14d;
let NewMfaRegistrations = AuditLogs
| where TimeGenerated > ago(LookbackDays)
| where OperationName in ("User registered security info", "User changed default security info", "Add user StrongAuthenticationMethod", "Update user StrongAuthenticationMethod")
| extend TargetUPN = tostring(TargetResources[0].userPrincipalName)
| extend ActorUPN = tostring(InitiatedBy.user.userPrincipalName)
| extend ActorIP = tostring(InitiatedBy.user.ipAddress)
| extend MethodDetail = tostring(TargetResources[0].modifiedProperties[0].newValue)
| project RegistrationTime=TimeGenerated, TargetUPN, ActorUPN, ActorIP, MethodDetail, OperationName;
AADSignInLogs
| where TimeGenerated > ago(LookbackDays)
| where ResultType == "0"
| where RiskLevelDuringSignIn in ("medium", "high") or RiskLevelAggregated in ("medium", "high") or RiskState == "atRisk"
| project SignInTime=TimeGenerated, TargetUPN=UserPrincipalName, SignInIP=IPAddress, AppDisplayName, RiskLevel=RiskLevelDuringSignIn, Location
| join kind=inner NewMfaRegistrations on TargetUPN
| where SignInTime > RegistrationTime and SignInTime < RegistrationTime + 7d
| project TargetUPN, RegistrationTime, OperationName, MethodDetail, ActorIP, SignInTime, SignInIP, AppDisplayName, RiskLevel, Location
| sort by SignInTime desc
Hunting — SPL
spl
index=azure sourcetype="azure:aad:audit"
    (operationName="User registered security info" OR operationName="Add user StrongAuthenticationMethod" OR operationName="Update user StrongAuthenticationMethod")
| eval reg_user=lower(targetResources{0}.userPrincipalName)
| eval actor_upn=lower(initiatedBy.user.userPrincipalName)
| eval actor_ip=initiatedBy.user.ipAddress
| eval method_detail=targetResources{0}.modifiedProperties{0}.newValue
| rename _time as reg_time
| join type=inner reg_user [
    search index=azure sourcetype="azure:aad:signin" resultType="0"
        (riskLevelDuringSignIn="medium" OR riskLevelDuringSignIn="high" OR riskState="atRisk")
    | eval reg_user=lower(userPrincipalName)
    | rename _time as signin_time
    | fields reg_user, signin_time, ipAddress, appDisplayName, riskLevelDuringSignIn, location
]
| where signin_time > reg_time AND signin_time < reg_time + 604800
| table reg_user, reg_time, operationName, method_detail, actor_ip, signin_time, ipAddress, appDisplayName, riskLevelDuringSignIn
| sort - signin_time

Hunt for processes unexpectedly loading winscard.dll, the Windows Smart Card Resource Manager API. Sykipot malware and similar adversary tools targeting smart card MFA load this DLL to enumerate inserted cards and proxy authentication through the hardware token. Legitimate callers are limited to lsass.exe, svchost.exe hosting SCardSvr service, LogonUI.exe, and specific vendor credential provider DLLs. Any other process loading winscard.dll is unusual and warrants investigation.

Hunting — KQL
kql
// Hunt for processes loading Smart Card API from unexpected callers
DeviceImageLoadEvents
| where Timestamp > ago(7d)
| where FileName =~ "winscard.dll"
| where InitiatingProcessFileName !in~ (
    "lsass.exe", "svchost.exe", "LogonUI.exe", "credentialuibroker.exe",
    "certutil.exe", "mmc.exe", "gpupdate.exe", "csp.exe",
    "explorer.exe", "taskhostw.exe", "wmiprvse.exe"
  )
| project Timestamp, DeviceName, AccountName,
    InitiatingProcessFileName, InitiatingProcessCommandLine,
    InitiatingProcessFolderPath, InitiatingProcessParentFileName
| sort by Timestamp desc
Hunting — SPL
spl
index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=7
    ImageLoaded="*\\winscard.dll"
    NOT (
        Image="*\\lsass.exe" OR Image="*\\svchost.exe" OR Image="*\\LogonUI.exe"
        OR Image="*\\credentialuibroker.exe" OR Image="*\\certutil.exe"
        OR Image="*\\mmc.exe" OR Image="*\\explorer.exe"
        OR Image="*\\taskhostw.exe" OR Image="*\\wmiprvse.exe"
    )
| table _time, host, User, Image, ParentImage, ImageLoaded, CommandLine
| sort - _time

Hunt for AiTM (adversary-in-the-middle) session token replay indicators: users with successful MFA authentications in a 1-hour window from multiple distinct IP addresses or countries, particularly from non-compliant or unregistered devices. AiTM phishing kits (Evilginx2, Modlishka) authenticate the user to the real service from their relay IP while sending stolen session tokens to the adversary at a different IP, creating a multi-IP signature for the same authentication session.

Hunting — KQL
kql
// Hunt for AiTM phishing indicators: sign-ins where session token was replayed from a different IP/device than original MFA
AADSignInLogs
| where TimeGenerated > ago(7d)
| where ResultType == "0"
| where AuthenticationRequirement == "multiFactorAuthentication"
| extend SignInIP = tostring(IPAddress)
| extend UserAgent = tostring(UserAgent)
| extend DeviceId = tostring(DeviceDetail.deviceId)
| extend IsCompliant = tostring(DeviceDetail.isCompliant)
| extend TrustType = tostring(DeviceDetail.trustType)
| where IsCompliant == "false" or TrustType == ""
| summarize
    SignInCount = count(),
    UniqueIPs = dcount(SignInIP),
    UniqueUserAgents = dcount(UserAgent),
    IPList = make_set(SignInIP),
    UAList = make_set(UserAgent),
    Apps = make_set(AppDisplayName),
    Countries = make_set(tostring(LocationDetails.countryOrRegion))
    by UserPrincipalName, bin(TimeGenerated, 1h)
| where UniqueIPs > 2 or UniqueUserAgents > 3
| extend CountryCount = array_length(Countries)
| where CountryCount > 1 or UniqueIPs > 2
| sort by UniqueIPs desc
Hunting — SPL
spl
index=azure sourcetype="azure:aad:signin"
    resultType="0" authenticationRequirement="multiFactorAuthentication"
| eval device_compliant=deviceDetail.isCompliant
| eval trust_type=deviceDetail.trustType
| eval country=locationDetails.countryOrRegion
| eval user_agent=userAgent
| eval src_ip=ipAddress
| bucket _time span=1h
| stats
    count as signin_count,
    dc(src_ip) as unique_ips,
    dc(user_agent) as unique_agents,
    values(src_ip) as ip_list,
    values(user_agent) as ua_list,
    dc(country) as unique_countries,
    values(country) as country_list,
    values(appDisplayName) as apps
    by _time, userPrincipalName
| where unique_ips > 2 OR (unique_countries > 1 AND signin_count > 1)
| sort - unique_ips

Atomic Red Team Tests

Test 1 MFA Fatigue Simulation via Repeated MSAL Authentication Requests
windows

Simulates a LAPSUS$-style MFA fatigue attack by generating repeated Microsoft authentication requests for a test account with push notification MFA enabled. Uses the Microsoft Authentication Library (MSAL) to trigger authentication flows that send push notifications to the enrolled device. The attack relies on the user eventually approving a prompt after receiving many unwanted notifications. Use only against a designated test account in a non-production tenant with explicit authorization.

Command

powershell
pip install msal --quiet && python3 -c "
import msal, time, sys
client_id = '04b07795-8ddb-461a-bbee-02f9e1bf7b46'
tenant_id = 'YOUR_TENANT_ID'
test_account = '[email protected]'
prompt_count = 10
app = msal.PublicClientApplication(client_id, authority=f'https://login.microsoftonline.com/{tenant_id}')
for i in range(prompt_count):
    print(f'[*] Sending MFA prompt {i+1}/{prompt_count} to {test_account}...')
    try:
        app.acquire_token_interactive(
            scopes=['User.Read'],
            login_hint=test_account,
            prompt='login',
            timeout=8
        )
    except Exception as e:
        print(f'    Prompt {i+1} result: {str(e)[:80]}')
    time.sleep(3)
print('[*] Fatigue simulation complete')
"

Cleanup

powershell
No persistent changes — authentication tokens are ephemeral and session state is not stored

Expected Telemetry

Azure AD Sign-In Logs (AADSignInLogs): 10 entries for the test account — each with ResultType indicating MFA prompt sent or denied, AuthenticationRequirement=multiFactorAuthentication, AuthenticationMethodsUsed=PhoneAppNotification. Events appear within a 3-5 minute window, all from the same source IP (the test machine). If any prompt is approved, a success event (ResultType=0) also appears.

Expected Detection

KQL MFA fatigue alert fires when FailCount reaches the MfaPromptThreshold (5) within the 30-minute window. The query returns the test account with FailCount approaching the prompt_count value. SPL query shows failed_attempts >= 5 with success_attempts = 0 (or 1 if a prompt is approved). Adjust threshold to 3 for testing if 5 feels too high for your lab environment.

Test 2 Smart Card API Enumeration via Custom Process
windows

Demonstrates detection of unexpected processes loading winscard.dll (Windows Smart Card Resource Manager API). Simulates Sykipot-style malware that loads the smart card API to enumerate inserted tokens and proxy certificate-based authentication through the hardware token. The executable loads winscard.dll and calls SCardListReaders to enumerate connected card readers, which should trigger Sysmon Image Load detection.

Command

powershell
powershell.exe -ExecutionPolicy Bypass -Command @"
$source = @'
using System;
using System.Runtime.InteropServices;
public class SmartCardProbe {
    [DllImport("winscard.dll", SetLastError=true)]
    public static extern uint SCardEstablishContext(uint dwScope, IntPtr pvReserved1, IntPtr pvReserved2, ref IntPtr phContext);
    [DllImport("winscard.dll", SetLastError=true)]
    public static extern uint SCardListReaders(IntPtr hContext, string mszGroups, IntPtr mszReaders, ref uint pcchReaders);
    [DllImport("winscard.dll", SetLastError=true)]
    public static extern uint SCardReleaseContext(IntPtr hContext);
    public static void Run() {
        Console.WriteLine('[*] Loading winscard.dll and enumerating smart card readers...');
        IntPtr ctx = IntPtr.Zero;
        uint ret = SCardEstablishContext(2, IntPtr.Zero, IntPtr.Zero, ref ctx);
        Console.WriteLine('[*] SCardEstablishContext returned: 0x' + ret.ToString('X8'));
        uint bufLen = 0;
        SCardListReaders(ctx, null, IntPtr.Zero, ref bufLen);
        Console.WriteLine('[*] Reader buffer size needed: ' + bufLen);
        SCardReleaseContext(ctx);
        Console.WriteLine('[*] Smart card enumeration complete - detection should have fired');
    }
}
'@
Add-Type -TypeDefinition $source -OutputAssembly "$env:TEMP\scard_probe.exe"
& "$env:TEMP\scard_probe.exe"
"@

Cleanup

powershell
Remove-Item "$env:TEMP\scard_probe.exe" -ErrorAction SilentlyContinue

Expected Telemetry

Sysmon Event ID 1 (Process Create): scard_probe.exe spawned from powershell.exe with the compilation command in ParentCommandLine. Sysmon Event ID 7 (Image Load): scard_probe.exe loading C:\Windows\System32\winscard.dll — InitiatingProcessFileName=scard_probe.exe is not in the allowlist of expected winscard.dll callers. Sysmon Event ID 11 (File Create): scard_probe.exe written to %TEMP%.

Expected Detection

KQL hunting query for unexpected winscard.dll loads fires — scard_probe.exe is not lsass.exe, svchost.exe, LogonUI.exe, or any other expected caller. SPL EventCode=7 query returns the image load event. EDR behavioral detection may also fire for unsigned binary loading smart card API.

Test 3 OTP Keylogger via Low-Level Keyboard Hook Installation
windows

Simulates the keylogging mechanism used to capture hardware token OTP codes and smart card PINs at time of entry. Installs a WH_KEYBOARD_LL low-level keyboard hook via SetWindowsHookEx for 5 seconds, captures keystrokes, then removes the hook. This pattern is consistent with Kimsuky's proprietary MFA interception tool and RSA SecurID token code capture malware. Execute only in an isolated test environment — do not run on systems processing real credentials.

Command

powershell
powershell.exe -ExecutionPolicy Bypass -NoProfile -Command @"
$src = @'
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
public class KeyloggerTest {
    private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll")] static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc fn, IntPtr hMod, uint threadId);
    [DllImport("user32.dll")] static extern bool UnhookWindowsHookEx(IntPtr hhk);
    [DllImport("user32.dll")] static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
    [DllImport("kernel32.dll")] static extern IntPtr GetModuleHandle(string name);
    [DllImport("user32.dll")] static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMin, uint wMax);
    [StructLayout(LayoutKind.Sequential)] public struct MSG { public IntPtr hwnd; public uint message; public IntPtr wParam; public IntPtr lParam; public int time; public int pt_x; public int pt_y; }
    static IntPtr _hook; static LowLevelKeyboardProc _proc; static int _count = 0;
    static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) {
        if (nCode >= 0 && wParam == (IntPtr)0x0100) { _count++; Console.Write("K"); }
        return CallNextHookEx(_hook, nCode, wParam, lParam);
    }
    public static void Install(int durationMs) {
        _proc = HookCallback;
        _hook = SetWindowsHookEx(13, _proc, GetModuleHandle(null), 0);
        Console.WriteLine("[*] WH_KEYBOARD_LL hook installed: " + _hook + " (PID: " + Process.GetCurrentProcess().Id + ")");
        Console.WriteLine("[*] Type any keys within 5 seconds to simulate OTP entry...");
        System.Threading.Thread.Sleep(durationMs);
        UnhookWindowsHookEx(_hook);
        Console.WriteLine("\n[*] Hook removed. Captured " + _count + " key events.");
    }
}
'@
Add-Type -TypeDefinition $src
[KeyloggerTest]::Install(5000)
"@

Cleanup

powershell
Process exits naturally; no persistent hooks remain after execution

Expected Telemetry

Sysmon Event ID 1 (Process Create): PowerShell with command line containing SetWindowsHookEx, WH_KEYBOARD_LL references — triggers on process create. Windows Security Event ID 4688 (if command-line audit enabled). PowerShell ScriptBlock Log Event ID 4104 captures the full hook installation code. Behavior-based EDR (CrowdStrike, Defender, SentinelOne) should generate a keyboard hook behavioral alert for the WH_KEYBOARD_LL hook type.

Expected Detection

EDR behavioral alert for low-level keyboard hook installation from PowerShell process. PowerShell ScriptBlock Logging (Event ID 4104) records SetWindowsHookEx and keyboard interception code. MDAV behavioral rules may trigger on keyboard hook installation from a scripting engine. This test also generates Sysmon process creation telemetry that feeds the related T1056.001 keylogging detection rules.

Test 4 Adversary MFA Phone Number Registration via Microsoft Graph API
windows

Simulates the Chimera threat group technique of registering an adversary-controlled phone number on a compromised account to intercept SMS-based OTP codes. Uses the Microsoft Graph API to enumerate existing MFA methods and demonstrate how an attacker with Account.ReadWrite access could register a rogue phone number. This atomic enumerates methods only (read-only) — the write command is shown commented out for documentation purposes.

Command

powershell
# Prerequisites: Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Users
# Run in PowerShell 5.1+ with authorization to test account only
$TestUserUPN = '[email protected]'
Write-Host '[*] Connecting to Microsoft Graph (requires UserAuthenticationMethod.Read.All scope)...'
Connect-MgGraph -Scopes 'UserAuthenticationMethod.Read.All' -NoWelcome 2>&1
Write-Host '[*] Enumerating MFA authentication methods for:' $TestUserUPN
$methods = Get-MgUserAuthenticationMethod -UserId $TestUserUPN
$methods | ForEach-Object {
    $type = $_.AdditionalProperties['@odata.type']
    $detail = if ($_.AdditionalProperties['phoneNumber']) { $_.AdditionalProperties['phoneNumber'] } elseif ($_.AdditionalProperties['displayName']) { $_.AdditionalProperties['displayName'] } else { 'N/A' }
    Write-Host "  Method: $type | Detail: $detail | ID: $($_.Id)"
}
Write-Host '[*] To register adversary phone (requires ReadWrite scope - DO NOT run in prod):'
Write-Host '    New-MgUserAuthenticationPhoneMethod -UserId' $TestUserUPN '-PhoneType mobile -PhoneNumber "+1 5551234567"'
Write-Host '[*] Enumeration complete - check Azure AD Audit Logs for this operation'
Disconnect-MgGraph

Cleanup

powershell
Disconnect-MgGraph — no changes made (read-only enumeration)

Expected Telemetry

Azure AD Audit Logs (AuditLogs table in Sentinel / azure:aad:audit in Splunk): OperationName='List user authentication methods' — the enumeration creates an audit event. If the write command is executed (in authorized lab only): OperationName='User registered security info' or 'Add user StrongAuthenticationMethod' with the new phone number value in TargetResources[0].modifiedProperties. Both events include the actor's IP address and UPN.

Expected Detection

Hunting query correlating MFA registration changes with subsequent risky sign-ins fires when the registration event precedes an authentication anomaly within 7 days. AuditLogs query for 'Add user StrongAuthenticationMethod' or 'User registered security info' returns the registration event. Microsoft Entra ID Protection may flag the account if the registration comes from an unfamiliar IP or during an active risk session.

Related Detections