Windows Event IDs by Scenario
Default log: Security unless stated. Event logs live in C:\Windows\System32\winevt\Logs\. Cross-check source AND destination hosts — lateral movement writes to both.
Authentication & Logons
| ID | Log | Meaning / investigative value | Notes · ATT&CK |
|---|---|---|---|
| 4624 | Security | Successful logon. Key fields: Logon Type, Account, Source IP/Workstation, Logon ID (correlate to 4634/4672). | See Logon Types table. T1078 |
| 4625 | Security | Failed logon. Status codes: 0xC000006A wrong password · 0xC0000064 user doesn't exist · 0xC0000234 locked out · 0xC0000072 disabled · 0xC000006F outside hours. | Bursts = brute force / spraying. T1110 |
| 4634 / 4647 | Security | Logoff / user-initiated logoff. Session duration via matching Logon ID with 4624. | Type 3 sessions log off almost instantly — normal. |
| 4648 | Security | Logon with explicit credentials (runas, /netonly, some pass-the-hash tooling). | Watch for user→admin cred switches. T1078, T1550.002 |
| 4672 | Security | Special privileges assigned at logon — fires for admin-equivalent logons. | Pair with 4624 same Logon ID. |
| 4776 | Security (DC / local host) | NTLM credential validation, logged by the machine that owns the account: the DC for domain accounts, the local host for local accounts. Error 0xC0000064 / 0xC000006A as above. | NTLM where Kerberos expected = suspicious. T1550.002 |
| NTLM 4024 / 4025 | Microsoft-Windows-NTLM/Operational | NTLMv1-derived credentials used for SSO (Win 11 24H2+ / Server 2025). 4024 = Warning (Audit mode, auth still succeeds) · 4025 = Error (blocked in Enforce mode). Shows target server, user/domain, mechanism OID. | Registry BlockNtlmv1SSO (LSA\msv1_0): 0 = Audit, 1 = Enforce. Microsoft plans default → Enforce from Oct 2026 if unset. Enhanced NTLM logging policies must be on. Downgrade/relay triage · T1550.002 |
| 4768 | Security (DC) | Kerberos TGT requested — account authenticated to domain. | Result 0x6 = bad username; 0x12 = disabled/locked. |
| 4769 | Security (DC) | Kerberos service ticket (TGS) requested for an SPN. | Kerberoasting: many 4769s, RC4 (0x17) tickets T1558.003 |
| 4771 | Security (DC) | Kerberos pre-auth failed. 0x18 = bad password. | Kerberos brute-force indicator. T1110 |
| 4740 | Security (DC) | Account locked out — includes caller computer name (source of failures). | Fast pivot to the attacking host. |
| 4778 / 4779 | Security | Session reconnected / disconnected (RDP + fast user switching). Includes client name and IP. | RDP session tracking even without full logon. T1021.001 |
Account & Group Manipulation
| ID | Log | Meaning / investigative value | Notes · ATT&CK |
|---|---|---|---|
| 4720 | Security | User account created. Subject = who created it. | Rogue local admin accounts. T1136 |
| 4722 / 4725 | Security | Account enabled / disabled. | |
| 4723 / 4724 | Security | Password change attempt (by user) / password reset (by admin or attacker). | 4724 on privileged account = takeover pattern. T1098 |
| 4726 | Security | Account deleted — cleanup of attacker-created accounts, or removal of legitimate users' access. | T1070.009 (cleanup) · T1531 (access removal) |
| 4728 / 4732 / 4756 | Security | Member added to global / local / universal security group. Watch Domain Admins, Administrators, Remote Desktop Users. | T1098 |
| 4738 | Security | User account changed (UAC flags, expiry, etc.). | |
| 4765 / 4766 | Security (DC) | SID History added to an account / attempt to add SID History failed. Logged on domain controllers under Audit User Account Management; fields: Subject (who), Target Account, Source Account, SID List. A changed SID History value also shows on 4738. | Outside a planned domain migration any 4765 or 4766 is suspicious — worst case a privileged SID (Domain Admins RID 512, Enterprise Admins RID 519) or a same-domain SID in the list. Offline edits of ntds.dit or a DCShadow push write no 4765, so also sweep the attribute: Get-ADObject -LDAPFilter "(sIDHistory=*)" -Properties sIDHistory · T1134.005 |
| 4781 | Security | Account renamed — e.g. rename + recreate of built-in accounts. | |
| 4794 | Security (DC) | Attempt to set the Directory Services Restore Mode (DSRM) administrator password (e.g. ntdsutil "set dsrm password"). Logged on domain controllers under Audit User Account Management, success or failure; fields: Subject (who), Caller Workstation, Status Code. | The DSRM account is the DC's local Administrator and password policy does not apply to it: a 4794 outside DC promotion or a documented procedure is a persistence red flag, worse if HKLM\SYSTEM\CurrentControlSet\Control\Lsa\DsrmAdminLogonBehavior is 1 or 2 (absent / 0 = DSRM logon only when booted into DSRM; 2 = usable any time while AD DS runs). Check that value on every DC · T1098 |
| 4741 / 4742 | Security (DC) | Computer account created / changed. Watch for machine accounts created by ordinary users (default quota is 10 per user) and changes to SPNs, DNS host name or delegation settings. | Precursor to RBCD and AD CS abuse (e.g. Certighost) · T1136.002 |
Process Execution & PowerShell
| ID | Log | Meaning / investigative value | Notes · ATT&CK |
|---|---|---|---|
| 4688 | Security | New process created — parent process, token elevation. Command line only if "Include command line in process creation events" GPO is on. | Not enabled by default T1059 |
| 4689 | Security | Process exited — bound the execution window. | |
| Sysmon | Sysmon/Operational | Process creation with full command line, hashes and parent, plus network, DLL, registry, pipe, WMI, DNS and file events. Every event ID, deployment, detection rules and blind spots are in the Sysmon section. | Better than 4688 where deployed; requires Sysmon to be installed or the built-in feature enabled. |
| 4104 | PowerShell/Operational | Script block logging — actual (de-obfuscated) code executed. The single best PowerShell artifact. | Level=Warning auto-logs "suspicious" blocks even if disabled. T1059.001 |
| 4103 | PowerShell/Operational | Module logging — pipeline execution detail. | |
| 400 / 403 | Windows PowerShell | Engine start / stop — PowerShell ran even on legacy systems. HostApplication field shows the command line. | Works back to PSv2. |
| 8003 / 8004 | AppLocker/EXE and DLL | EXE/DLL allowed but would have been blocked (audit mode) / blocked. Scripts and MSI files log 8006 / 8007 in AppLocker/MSI and Script. | Only if AppLocker policies applied. |
| 3076 / 3077 | CodeIntegrity/Operational | App Control for Business (WDAC) policy: file would have been blocked (audit mode) / was blocked (enforced). Each signature of the file gets a 3089, matched by the Correlation ActivityID in the System data. Scripts and MSI files checked by script hosts log 8028 / 8029 (signatures in 8038) in AppLocker/MSI and Script. | Only if an App Control policy is deployed. In audit mode, 3076 is a record of unapproved binaries that loaded. An 8029 can still let the script run in restricted mode. |
▸ How to read this: AppLocker vs App Control, audit vs enforced
Two Windows application-control features write the block and audit events above, and each one has an audit mode and an enforced mode. Find the line for the feature and the kind of file, then read across: the column tells you whether the file ran.
- AppLocker — EXE and DLL rules log 8003 (allowed, but would have been blocked) and 8004 (blocked) in AppLocker/EXE and DLL. MSI and script rules log 8006 / 8007 in AppLocker/MSI and Script.
- App Control for Business (WDAC) — files log 3076 (audit mode, would have been blocked) and 3077 (enforced, blocked) in CodeIntegrity/Operational, plus one 3089 for each signature of the file; match them on the Correlation ActivityID in the System data. Scripts and MSI files checked by script hosts log 8028 / 8029, with signatures in 8038, in AppLocker/MSI and Script.
Audit mode stops nothing, so 3076 and 8003 are a list of unapproved code that actually loaded or ran on the host. An 8029 is a block, but the script can still run in restricted mode. None of these events exist unless a policy is deployed, so an empty log is not evidence that nothing ran.
Services & Scheduled Tasks
| ID | Log | Meaning / investigative value | Notes · ATT&CK |
|---|---|---|---|
| 7045 | System | New service installed — service name, binary path, account. PsExec creates PSEXESVC; many C2s install odd one-word services. | T1543.003, T1569.002 |
| 4697 | Security | Service installed (Security-side twin of 7045). | Requires audit subcategory. |
| 7034 / 7035 / 7036 / 7040 | System | Service crashed / start-stop control / state change / start-type changed. | 7040 to "disabled" on AV/EDR services = tampering. T1685 |
| 4698 / 4699 | Security | Scheduled task created / deleted — full task XML embedded in the event. | T1053.005 |
| 4700 / 4701 / 4702 | Security | Task enabled / disabled / updated. | |
| 106 / 140 / 141 | TaskScheduler/Operational | Task registered / updated / deleted. | Disabled by default on newer Win10+. |
| 200 / 201 | TaskScheduler/Operational | Task action started / completed — proves the task actually ran. |
RDP & Remote Access
| ID | Log | Meaning / investigative value | Notes · ATT&CK |
|---|---|---|---|
| 1149 | TS-RemoteConnectionManager/Operational | RDP network connection reached the host — user + source IP. Fires before auth completes. | Connection attempt, not proof of logon. T1021.001 |
| 21 / 22 | TS-LocalSessionManager/Operational | Session logon succeeded / shell start — user, session, source IP. | The definitive interactive RDP record. |
| 23 / 24 / 25 | TS-LocalSessionManager/Operational | Logoff / disconnect / reconnect — build the full session timeline. | |
| 4624 type 10 | Security | RemoteInteractive logon = RDP. | Type 7 reconnects to existing session. |
| 98 / 131 | RdpCoreTS/Operational | TCP connection accepted (131 includes client IP) / successful connection (98). | Survives when other logs are cleared selectively. |
▸ How to read this: one RDP session, log by log
Each RDP event above answers one question about one stage of the session, and each stage writes to a different log on the RDP host. Read the figure left to right as time, top to bottom as log files.
- Connect: 98 / 131 (RdpCoreTS) show the TCP connection and client IP; 1149 shows a user and source IP reached the host, but fires before authentication.
- Authenticate: 4624 type 10 in Security is the RDP logon; failures land in 4625.
- Session: 21 / 22 (LocalSessionManager) confirm the session logon and shell start.
- Disconnect / reconnect: 4778 / 4779 and 23 / 24 / 25 mark the session dropping and resuming, then 4634 / 4647 and 23 close it. Pair them with the logon to get session duration.
On the client, RDP client MRU, RDP bitmap cache, Sysmon 3 and 4648 show where the session went and with which credentials. If logs were cleared selectively, 98 / 131 in RdpCoreTS can still show the connection.
▸ How to read this: lateral movement leaves evidence on both hosts
One hop from host A (source) to host B (destination) is recorded in two places. Use the destination to prove that something arrived and the source to learn who or what launched it.
- Destination (B) — the logon (4624 type 3 for SMB/WMI/WinRM, type 10 for RDP), the share touched (5140 / 5145), and the method's own trail: RDP 1149, 21 / 22, 98 / 131; WinRM 91 / 168; service install 7045 / 4697 (PsExec writes PSEXESVC).
- Source (A) — explicit-credential use (4648), the process that opened the connection (Sysmon 3), and the RDP client MRU in NTUSER.DAT.
Pivot between hosts on the source IP in 4624/5140 and on the account name. Row-by-row detail is in the tables below.
Lateral Movement, Shares & WMI
| ID | Log | Meaning / investigative value | Notes · ATT&CK |
|---|---|---|---|
| 5140 | Security | Network share accessed — share name + source IP. | ADMIN$ / C$ / IPC$ access = admin lateral movement T1021.002 |
| 5145 | Security | Detailed share access — relative file path requested on the share. PsExec writes PSEXESVC.exe to ADMIN$. | Very verbose; often on DCs only. |
| 4624 type 3 | Security | Network logon — SMB, WMI, WinRM, mapped drives on the destination host. | The destination-side fingerprint of most lateral movement. |
| 5857 | WMI-Activity/Operational | WMI provider loaded. | |
| 5859 / 5860 / 5861 | WMI-Activity/Operational | WMI event subscription activity — 5861 = permanent subscription (filter + consumer) = persistence. | T1546.003 |
| 91 / 168 | WinRM/Operational | WinRM session created / authenticating user — PowerShell Remoting trail. | T1021.006 |
| 59 / 60 | Bits-Client/Operational | BITS transfer started (59, includes URL) / stopped (60, URL + status code). Job creation is event 3. | Malware download via bitsadmin. T1197 |
Defense Evasion & Log Tampering
| ID | Log | Meaning / investigative value | Notes · ATT&CK |
|---|---|---|---|
| 1102 | Security | Security log cleared — records who did it. Cannot be suppressed while auditing runs. | Almost never legitimate T1685.005 |
| 1100 | Security | Event logging service shut down. Normal during a system shutdown; at any other time it marks a deliberate stop of logging. | Look for a log gap after it with no 1102 · T1685.001 |
| 104 | System | Any event log cleared (System-side record, names the log). | T1685.005 |
| 4719 | Security | System audit policy changed — auditing dialed down before action. | T1685.001 |
| 1116 / 1117 | Windows Defender/Operational | Malware detected / action taken — name, path, hash, user. | Detection history even if later disabled. |
| 5001 / 5007 | Windows Defender/Operational | Real-time protection disabled / config changed (exclusions added). | Exclusion paths reveal attacker working dirs T1685 |
| 2004 / 2005 · 2071 / 2097 | Windows Firewall/Firewall | Firewall rule added (2004) / modified (2005). Newer Windows 10/11 builds log rule additions as 2071 or 2097; Microsoft has not documented which build uses which, so hunt all three. | Inbound allows for C2/RDP. T1686.003 |
Sysmon
Log: Microsoft-Windows-Sysmon/Operational (file Microsoft-Windows-Sysmon%4Operational.evtx). What gets logged depends entirely on the installed config. The UtcTime field is UTC and is when the activity happened. Join events on ProcessGuid, not the PID.
▸ How to read this: one ProcessGuid, many events
Sysmon is a boot-start driver plus a service that write to their own event log. It records; it does not block, except for Sysmon 27 and Sysmon 28. The XML config decides what is kept, so the same event ID can be complete on one host and filtered out on the next.
- Start at Sysmon 1: command line, parent, user, hashes and a
ProcessGuidthat stays unique even when Windows reuses the PID. - Follow the ProcessGuid into the stage events in the figure: files and DNS on delivery, DLLs and injection on execution, registry, WMI and drivers for persistence, Sysmon 10 on LSASS, connections and pipes for lateral movement.
- Check Sysmon itself: Sysmon 4 (service state), Sysmon 16 (config change) and Sysmon 255 (errors) tell you whether the record is complete for your time window.
Before calling something absent, read the active config (sysmon64 -c). An exclude rule is not evidence of innocence.
Event reference
| ID | Event (config tag) | Key fields · investigative value | Notes · ATT&CK |
|---|---|---|---|
| Sysmon 1 | Process creation (ProcessCreate) | Full command line of the process and its parent, user, integrity level, hashes, OriginalFileName and ProcessGuid. Better than 4688 where deployed. | OriginalFileName comes from the PE version info, so it exposes renamed binaries. T1059 |
| Sysmon 2 | File creation time changed (FileCreateTime) | A process explicitly set a file's creation time: CreationUtcTime (new) vs PreviousCreationUtcTime. A new value years older than the previous one on a fresh executable is timestomping. | Creation time only. Installers, archivers and sync clients do this legitimately. T1070.006 |
| Sysmon 3 | Network connection (NetworkConnect) | TCP/UDP connection by process: image, user, Initiated, source and destination IP, port and hostname. | Disabled by default: needs a config rule. C2 beaconing hunts. T1071 |
| Sysmon 4 | Sysmon service state changed | The Sysmon service started or stopped (State). | Cannot be filtered. Stopped outside maintenance = possible tampering. T1685.001 |
| Sysmon 5 | Process terminated (ProcessTerminate) | UtcTime, ProcessGuid, ProcessId — bounds a process's lifetime with Sysmon 1. | Often excluded to save volume. |
| Sysmon 6 | Driver loaded (DriverLoad) | Kernel driver loaded, with hashes and signature status (checked after loading). | Vulnerable drivers used for BYOVD are validly signed: match hashes against LOLDrivers, not the signature. T1068 |
| Sysmon 7 | Image loaded (ImageLoad) | DLL loaded into a process, with hashes and signature. Unsigned DLLs in odd paths; side-loading next to a signed EXE. | Off by default and very noisy: scope it to specific DLLs or processes. T1574.001 |
| Sysmon 8 | CreateRemoteThread | A process created a thread in another process: source, target, StartAddress, StartModule, StartFunction. Classic injection. | Empty StartModule = thread starts outside any loaded module (typical of shellcode). T1055 |
| Sysmon 9 | RawAccessRead | A process read a drive or volume through the \\.\ device path. Used to copy locked files (NTDS.dit, SAM, hives) and to bypass file-access auditing. | Expected from System, backup, AV and defrag tools. T1006 |
| Sysmon 10 | ProcessAccess | A process opened another: SourceImage, TargetImage, GrantedAccess, CallTrace. GrantedAccess to lsass.exe = credential dumping. | CallTrace through dbghelp.dll / dbgcore.dll = MiniDumpWriteDump. Filter expected accessors (AV, EDR) by full path. T1003.001 |
| Sysmon 11 | File created (FileCreate) | File created or overwritten: image, TargetFilename, creation time. Drops in Startup, Temp and Downloads. | Pair with Sysmon 22 and 15 for where the file came from. T1105 |
| Sysmon 12 | Registry key / value created or deleted (RegistryEvent) | TargetObject and the process. Root keys are abbreviated: HKLM, HKU, HKCR; ControlSet00x shows as CurrentControlSet. | Watch autostart keys. T1112 |
| Sysmon 13 | Registry value set (RegistryEvent) | TargetObject plus Details, the data written (e.g. the Run-key command line, or DWORD (0x00000002)). | The workhorse for registry persistence. T1547.001 |
| Sysmon 14 | Registry key / value renamed (RegistryEvent) | TargetObject and NewName: a key or value renamed into place. | Rare; worth reviewing on servers. |
| Sysmon 15 | File stream created (FileCreateStreamHash) | A named (alternate) stream was created: hash of the file plus the stream's contents. For Zone.Identifier that shows HostUrl / ReferrerUrl — where a download came from. | A PE hidden in an ADS has a non-zero IMPHASH (needs IMPHASH in HashAlgorithms). T1564.004 |
| Sysmon 16 | Sysmon configuration change | The Sysmon config was changed or reloaded; records the configuration file and its hash. | Cannot be filtered. Compare the hash with your fleet baseline. T1685.001 |
| Sysmon 17 | Named pipe created (PipeEvent) | PipeName and the server process. | C2 frameworks and remote-exec tools use default pipe names (see detection rules). |
| Sysmon 18 | Named pipe connected (PipeEvent) | PipeName and the client process. | Pair 17 (server) with 18 (client) to see who talks to whom. |
| Sysmon 19 | WMI event filter (WmiEvent) | WMI event filter registered: namespace, name and WQL query (the trigger). | T1546.003 |
| Sysmon 20 | WMI event consumer (WmiEvent) | WMI consumer registered: name, type and Destination — the command or script that will run. | Destination running cmd, PowerShell or script = persistence T1546.003 |
| Sysmon 21 | WMI consumer-to-filter binding (WmiEvent) | Consumer bound to filter: consumer and filter paths. With 19 and 20 this completes a permanent WMI subscription. | T1546.003 |
| Sysmon 22 | DNS query (DnsQuery) | Process, QueryName, status and results — logged whether the lookup succeeds or fails, cached or not. | Relies on Windows DNS client telemetry: DoH, direct-to-IP and custom resolvers are not seen. T1071.004 |
| Sysmon 23 | File delete archived (FileDelete) | File deleted and a copy kept in the ArchiveDirectory (default \Sysmon at the volume root, SYSTEM-only ACL). Recovers deleted tools and scripts. | Grows fast: scope it, or use 26. T1070.004 |
| Sysmon 24 | Clipboard change (ClipboardChange) | New clipboard content; the content is stored in the ArchiveDirectory. | Only when clipboard capture is configured. Can hold pasted passwords — handle as sensitive. T1115 |
| Sysmon 25 | Process tampering (ProcessTampering) | Process image changed — hollowing or herpaderping (Type e.g. "Image is replaced"). | Some browsers and updaters trigger it: filter by path. T1055.012 |
| Sysmon 26 | File delete logged (FileDeleteDetected) | File deleted; logged without keeping a copy. | The cheaper default for deletion logging. T1070.004 |
| Sysmon 27 | File block executable (FileBlockExecutable) | Sysmon blocked a PE file from being written where the config forbids it. | One of only two Sysmon events that block rather than log. |
| Sysmon 28 | File block shredding (FileBlockShredding) | Sysmon blocked file shredding by tools such as SDelete. | Protects evidence from wiping. T1485 |
| Sysmon 29 | FileExecutableDetected | A new PE (MZ header) file was written to disk; logs image, TargetFilename, hashes. Added in Sysmon 15; needs the event enabled in config. | Dropper / staged-tool hunting. T1105 |
| Sysmon 255 | Error | Internal Sysmon error: heavy load, a bug, or security and integrity conditions not met. | Config-update failures here are a tampering signal (Sigma: "Sysmon Configuration Error"). |
Responder checklist
| Check | Where / command | What it tells you | Notes |
|---|---|---|---|
| Is Sysmon running? | fltmc · Get-WinEvent -ListLog *Sysmon* | The Sysmon driver always loads at altitude 385201, even when the driver and service are renamed. | Trust the altitude, not the name (the DriverName option renames both). |
| Offline image | C:\Windows\System32\winevt\Logs\Microsoft-Windows-Sysmon%4Operational.evtx · SYSTEM hive ControlSet00x\Services\<driver>\Parameters | The log, plus the installed rules (binary Rules value, readable only by admins) and hashing options. | In an offline hive, pick the control set named by Select\Current. |
| What was logged? | sysmon64 -c | With no file argument, prints the active config. | Read excludes before concluding "no evidence". |
| Config and service history | Sysmon 16 · Sysmon 4 · Sysmon 255 | When the config changed (with its hash), and when the service started or stopped, inside the incident window. | Unexpected config hash = check for a swapped config. |
| Collect | wevtutil epl Microsoft-Windows-Sysmon/Operational C:\IR\sysmon.evtx · \Sysmon\ archive | Export the live log. If 23 or 24 is on, the archive folder holds deleted files and clipboard data. | The archive is SYSTEM-only: psexec -sid cmd to read it. |
| Parse | EvtxECmd (Sysmon maps) · Hayabusa · Chainsaw · Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Sysmon/Operational'; Id=1} | EvtxECmd flattens fields to CSV for timelines; Hayabusa and Chainsaw run Sigma rules straight over the evtx. | Filter on Id before parsing a large log. |
| Pivot | ProcessGuid · ParentProcessGuid · LogonId | Build the process tree across 1, 3, 7, 11, 22 and more; LogonId joins to Security 4624. | PIDs are reused; GUIDs are not. |
| Two timestamps | UtcTime vs the event's TimeCreated | UtcTime is when it happened; TimeCreated is when the service wrote it. | Boot-time activity captured by the driver is written later, when the service starts. |
Detection rules (rule logic)
| Detection | Sysmon event · logic | Why · false positives | SigmaHQ · ATT&CK |
|---|---|---|---|
| LSASS memory access | Sysmon 10: TargetImage ends \lsass.exe AND GrantedAccess contains 0x1038 / 0x1438 / 0x143a / 0x1fffff AND CallTrace contains dbghelp.dll or dbgcore.dll — or UNKNOWN( (unbacked code). | Mimikatz-style reads and MiniDumpWriteDump dumps. FP: AV, EDR, backup and Sysmon itself — exclude by full SourceImage path. | "Potential Credential Dumping Activity Via LSASS" T1003.001 |
| LSASS dump via comsvcs.dll | Sysmon 1: rundll32 with comsvcs and full in CommandLine, plus MiniDump or ordinal #24. | Living-off-the-land LSASS dump. Hardly any legitimate use. | "Process Memory Dump Via Comsvcs.DLL" T1003.001 |
| Office spawns a shell or LOLBin | Sysmon 1: ParentImage ends WINWORD / EXCEL / POWERPNT / ONENOTE / MSACCESS AND OriginalFileName is Cmd.Exe, PowerShell.EXE, cscript, MSHTA, RUNDLL32, REGSVR32, CertUtil, bitsadmin or schtasks. | Malicious document → payload. OriginalFileName catches renamed copies. FP: add-ins and business macros. | "Suspicious Microsoft Office Child Process" T1566.001 T1204.002 |
| Encoded PowerShell | Sysmon 1: OriginalFileName PowerShell.EXE or pwsh.dll AND CommandLine contains -e AND a base64 prefix such as JAB ($), SQBFAFgA or aQBlAHgA (IEX / iex in UTF-16). | Hides the real script; pair with 4104 for the decoded block. FP: some management agents. | "Suspicious Encoded PowerShell Command Line" T1059.001 T1027 |
| Certutil / BITS download | Sysmon 1: CertUtil with urlcache, verifyctl or URL plus http; bitsadmin /transfer. Then Sysmon 11 / 29 for the file landing. | LOLBin tool transfer. FP: rare; some legacy scripts. | "Suspicious Download Via Certutil.EXE" T1105 |
| WMI remote execution | Sysmon 1: ParentImage ends \wbem\WmiPrvSE.exe AND Image is cscript, wscript, mshta, rundll32, regsvr32 or certutil — or cmd.exe with powershell / mshta / rundll32 in CommandLine. | wmic /node, Impacket wmiexec and similar run commands under WmiPrvSE. FP: SCCM and other management tools. | "Suspicious WmiPrvSE Child Process" T1047 |
| Web shell | Sysmon 1: ParentImage w3wp.exe, httpd.exe, nginx.exe, php-cgi.exe, tomcat or java.exe running Catalina, spawning cmd, powershell, whoami, net, certutil and similar. | Web servers rarely start shells. FP: some admin plugins and health checks. | "Suspicious Process By Web Server Process" T1505.003 |
| Remote thread injection | Sysmon 8: TargetImage notepad, calc, mspaint, spoolsv, sethc and other unlikely targets (or lsass), from a non-system source; empty StartModule raises confidence. | Code injected into a benign host process. FP: accessibility, VM tools and printer software. | "Remote Thread Creation In Uncommon Target Image" T1055 |
| Process hollowing | Sysmon 25: Type = "Image is replaced" for an image outside Program Files. | The process on disk is not the process in memory. FP: some browsers and updaters. | "Potential Process Hollowing Activity" T1055.012 |
| DLL side-loading | Sysmon 7: a commonly abused DLL (e.g. libcef.dll, coreclr.dll) or a System32 DLL name loaded from \Users\Public, \Windows\Temp, \PerfLogs, \ProgramData or user profile folders; Signed = false raises confidence. | Signed EXE + planted DLL = trusted process runs attacker code. FP: per-user apps (Teams, browsers) — allowlist them by path. | "Abusable DLL Potential Sideloading From Suspicious Location" T1574.001 |
| Vulnerable driver (BYOVD) | Sysmon 6: Hashes match the LOLDrivers list, or the driver loads from a Temp or user path. | Signed-but-vulnerable drivers are used to kill EDR or gain kernel access. FP: old hardware utilities. | "Vulnerable Driver Load" T1068 |
| Default C2 / remote-exec pipes | Sysmon 17 / 18: PipeName like \MSSE-*-server, or starting \postex_, \status_, \msagent_ (Cobalt Strike defaults); \PSEXESVC, \RemCom. | High confidence when it hits, but mature operators rename pipes — no hit proves nothing. FP: legitimate PsExec use. | "CobaltStrike Named Pipe" T1021.002 |
| WMI event subscription | Sysmon 19 / 20 / 21: any new filter, consumer or binding outside known management software. | Fileless persistence that survives reboot. Destination with powershell, cmd or VBScript = high. | "WMI Event Subscription" T1546.003 |
| Autostart registry keys | Sysmon 13: TargetObject under \SOFTWARE\Microsoft\Windows\CurrentVersion\ Run, RunOnce or Policies\Explorer\Run, Winlogon Userinit / Shell, or Image File Execution Options \Debugger; Details pointing to AppData, Temp or Public, or running powershell / mshta / rundll32. | Classic logon persistence. FP: updaters (OneDrive, Teams) — Sigma filters many. | "CurrentVersion Autorun Keys Modification" T1547.001 T1546.012 |
| Service binary in odd folder | Sysmon 13: HKLM\System\CurrentControlSet\Services\*\ImagePath set to \Users\Public, \PerfLogs, \Temp or ADMIN$ paths. | PsExec-style and malicious services. Pair with System 7045. | "Service Binary in Suspicious Folder" T1543.003 |
| Executable dropped in user paths | Sysmon 29 (or Sysmon 11 ending .exe / .dll / .scr): TargetFilename in Downloads, AppData\Local\Temp, \Users\Public, \ProgramData, \Windows\Temp or \PerfLogs, written by a browser, Office, a script host or an archiver. | Second-stage drop. FP: installers and updaters — tune by writing process. | file_executable_detected / file_event rules T1105 |
| Executable hidden in an ADS | Sysmon 15: Hash contains a non-zero IMPHASH= (only PE files have one). | A program stored in an alternate data stream. Needs IMPHASH in HashAlgorithms. | "Hidden Executable In NTFS Alternate Data Stream" T1564.004 |
| LOLBin talks to the internet | Sysmon 3: Image rundll32 or regsvr32 (also mshta, msbuild) with Initiated = true to a non-private IP; Sysmon 22 lookups by regsvr32. | Proxy execution fetching payloads or beaconing. FP: some Microsoft-range traffic (Sigma filters it). | "Rundll32 Internet Connection" T1218 |
| Tunnel and anonymiser DNS | Sysmon 22: non-browser process resolving cloudflared / trycloudflare, devtunnels, .onion or external-IP lookup services. | Tunnels into the network and C2 that blends with SaaS. FP: developers and IT tooling. | "DNS Query To Devtunnels Domain" and siblings T1572 |
| Raw disk access | Sysmon 9: Image other than System, backup, AV or defrag tools reading a volume device. | Copying NTDS.dit, SAM or hives around file locks. FP: disk and forensic utilities. | "Potential Defense Evasion Via Raw Disk Access By Uncommon Tools" T1006 |
| Timestomped executable | Sysmon 2: TargetFilename .exe / .dll / .ps1 / .bat in System32, ProgramData or user folders, new CreationUtcTime earlier than PreviousCreationUtcTime, Image not an installer, browser, sync client or archiver. | Backdating a dropped tool. No dedicated SigmaHQ rule: tune locally. See Timestomping rows in Anti-Forensics. | T1070.006 |
| Sysmon discovery and unload | Sysmon 1: fltmc with unload; findstr / find with 385201; sysmon -u or -c run by an unexpected user. | Attackers check for Sysmon, then blind it. FP: real admin changes — check change records. | "Sysmon Driver Unloaded Via Fltmc.EXE" T1685.001 T1518.001 |
Blind spots & tampering
| Gap / attack | What happens | What still catches it | ATT&CK |
|---|---|---|---|
| Readable exclusions | Admins can read the rules (sysmon64 -c, the Parameters Rules value) and name or place tools to match an exclude. | Exclude on full path plus parent, never on file name alone; review end with / contains excludes. An image-name exclude is an evasion hole. | T1518.001 |
| Driver unloaded | fltmc unload (or a tool calling the same filter-unload API, e.g. Shhmon): kernel events stop while the service still runs. | The fltmc command itself (Sysmon 1 / 4688) just before the gap; a per-host event-rate drop in the SIEM. | T1685.001 |
| Config swapped | sysmon64 -c evil.xml installs a config that drops the attacker's activity. | Sysmon 16 with a new hash; alert when it differs from the baseline. | T1685.001 |
| Service blinded | Tools such as SysmonEnte open the Sysmon process to tamper with event reporting, so events silently stop. | Sysmon 10 with TargetImage Sysmon64.exe (Sigma "HackTool - SysmonEnte Execution"); heartbeat gaps. | T1685.001 |
| Log cleared | wevtutil cl Microsoft-Windows-Sysmon/Operational wipes local history. | System 104 names the cleared channel; the command shows in Sysmon 1 / 4688; forwarded copies survive. | T1685.005 |
| Command-line spoofing | A process started with fake arguments then rewritten in memory: Sysmon 1 records the fake ones. | Image, OriginalFileName, hashes and child behaviour; the PEB in a memory image. | T1564.010 |
| DNS outside the client | DoH in browsers, direct-to-IP C2 and custom resolvers never reach Sysmon 22. | Sysmon 3, proxy, firewall and DNS-server logs. | T1572 |
| Events off or narrowed | 3 and 7 are off by default; 10, 11 and 22 are usually narrowed; a config can drop anything. | 4688 with command line, Prefetch, Amcache and EDR fill the gaps. |
Implementation guide
| Step | Command / setting | What it does | Notes |
|---|---|---|---|
| Install (standalone) | sysmon64 -accepteula -i config.xml | Installs the service and a boot-start driver; no reboot. Without a config: SHA1 image hashes, no network logging. | Sysmon v15.22 (Sept 2026). Docs list Windows 11 and Server 2019 or later. Sysmon64a.exe on ARM64. |
| Built-in Sysmon (Win 11) | Settings › System › Optional features › More Windows features · Dism /Online /Enable-Feature /FeatureName:Sysmon · then sysmon -i [config.xml] | Sysmon ships as an optional, disabled-by-default Windows feature (Insider builds from February 2026; also announced for Server 2025). Enabling it does not start logging: initialize it from an elevated prompt. Same log channel as the standalone tool. | Uninstall standalone Sysmon first — the two cannot run together. Don't assume Sysmon is absent: check optional features. Verify on your build. |
| Update / dump config | sysmon64 -c config.xml · sysmon64 -c · sysmon64 -c -- | Apply a new config without reinstalling · print the active config · reset to defaults. | Every change logs Sysmon 16. |
| Schema / uninstall | sysmon64 -s · sysmon64 -u [force] | Print the schema (every event, field and type) · remove service and driver. | The config's schemaversion is separate from the binary version. |
| Config structure | <Sysmon schemaversion> › <HashAlgorithms> › <EventFiltering> › <RuleGroup groupRelation="or"> › <ProcessCreate onmatch="include"> | Rules on the same field are OR; different fields are AND; a RuleGroup with and forces AND. Exclude beats include. | Conditions: is, is any, is not, contains (any / all), excludes (any / all), begin with, end with, not begin / end with, less / more than, image. |
| Rule names → ATT&CK | <Image name="technique_id=T1218" condition="image">rundll32.exe</Image> | The matching rule's name is written to the event's RuleName field. | Lets the SIEM read the technique straight from the event (sysmon-modular style). |
| Hashes | <HashAlgorithms>SHA256,IMPHASH</HashAlgorithms> | MD5, SHA1, SHA256, IMPHASH or * (all). IMPHASH clusters builds of the same malware. | Set it explicitly; IMPHASH also powers the ADS-executable rule. |
| Deletion archive | ArchiveDirectory · CopyOnDeletePE · CopyOnDeleteExtensions · CopyOnDeleteSIDs · CopyOnDeleteProcesses | Controls what Sysmon 23 keeps; default folder Sysmon at each volume root, SYSTEM-only. | Use 26 instead of 23 where disk space matters. |
| Other entries | CheckRevocation · DnsLookup · DriverName | Signature revocation checks (default on) · reverse DNS for Sysmon 3 (default on) · rename the driver and service. | A renamed driver still loads at altitude 385201. |
| Baseline configs | SwiftOnSecurity sysmon-config · Olaf Hartong sysmon-modular · Neo23x0 sysmon-config fork | Commented starting point · modules with ATT&CK rule names, merged into ready configs · fork with newer threat rules. | Start from one, test, then tune. Never ship a stock file untested. |
| Tuning | Busiest: 3, 7, 10, 11, 12 / 13, 22 | Exclude known-good by full path plus parent or signature; include high-value targets (LSASS, autostart keys, user-writable paths). | Measure events per host per day before and after each change. |
| Log size & forwarding | wevtutil gl Microsoft-Windows-Sysmon/Operational · wevtutil sl Microsoft-Windows-Sysmon/Operational /ms:1073741824 | Check and raise the maximum log size (bytes, multiple of 64 KB); forward with WEF / WEC or a SIEM agent. | Local logs roll over fast on busy hosts — forwarding is what survives an incident. |
| Deploy at scale | GPO, Intune or SCCM · one baseline plus role overrides (DC, server, workstation) | Same binary and config hash everywhere you expect it. | Keep the XML in version control; track the Sysmon 16 hash per host. |
| Validate | Atomic Red Team (Invoke-AtomicTest) | Run a test per technique and confirm the expected event and RuleName reach the SIEM. | Re-test after every config change. |
Logon Types (4624 / 4625)
▸ How to read this: logon type = how the user reached the machine
The Logon Type field in 4624 / 4625 tells you how the session was created, so read it before the account name. Group the types by route: at the keyboard (2, 7, 11, 13), over the network (3, 8), remote desktop (10, 12), started by the system (4, 5) and alternate outbound credentials (9).
The last column of the table matters for scoping: types that leave credentials in memory (2, 4, 5, 9, 10) can be harvested from that host; Type 3 usually does not; Type 8 exposes the password itself. Types 7, 11, 12 and 13 are marked “—” in the table.
| Type | Name | What it means in practice | Creds cached in memory? |
|---|---|---|---|
| 2 | Interactive | Console logon at the keyboard (or via KVM). | Yes |
| 3 | Network | SMB shares, WMI, WinRM, most lateral movement (destination side). | No (usually) |
| 4 | Batch | Scheduled task execution. | Yes |
| 5 | Service | Service start under a configured account. | Yes |
| 7 | Unlock | Workstation unlocked — user was physically present again. | — |
| 8 | NetworkCleartext | Network logon with cleartext creds (IIS basic auth, some PowerShell). | Password exposed in memory |
| 9 | NewCredentials | runas /netonly — alternate creds for outbound only. Common in attacker tradecraft (e.g. overpass-the-hash). | Yes (alternate set) |
| 10 | RemoteInteractive | RDP / Terminal Services. | Yes |
| 11 | CachedInteractive | Logon with cached domain creds (DC unreachable) — e.g. laptop off-network. | — |
| 12 | CachedRemoteInteractive | RDP logon validated with cached credentials (Microsoft: “same as RemoteInteractive”). | — |
| 13 | CachedUnlock | Workstation unlock validated with cached credentials. | — |
Kerberos — Events, Codes & Attacks
All Kerberos auth events live in the Security log on Domain Controllers. Correlate 4768 (TGT) → 4769 (service ticket) → 4770 (renewal) to reconstruct a session. Encryption type in the event is the fastest attack tell.
▸ How to read this: the Kerberos flow in 3 hops
- AS exchange — the client proves who it is to the DC and gets a TGT. The DC logs 4768 (or 4771 if pre-auth fails).
- TGS exchange — the client presents the TGT and asks for a ticket to one service (SPN). The DC logs 4769.
- AP exchange — the client hands the service ticket to the target server, which logs the logon itself (e.g. 4624 type 3). The DC never sees this hop.
Forged tickets show up as gaps in this chain: a Golden Ticket skips hop 1 (4769 with no 4768); a Silver Ticket skips hops 1–2 on the DC (only the service logs anything).
Ticket & Auth Events
| ID | Meaning | Key fields | Notes · ATT&CK |
|---|---|---|---|
| 4768 | TGT requested — account authenticated to the domain (AS-REQ). | Account, Client IP, Ticket Encryption Type, Result Code, Pre-Auth Type. | First event of any Kerberos session. |
| 4769 | Service ticket requested for an SPN (TGS-REQ). | Account, Service Name (SPN), Client IP, Encryption Type, Failure Code. | Volume of 4769 + RC4 (0x17) = Kerberoasting T1558.003 |
| 4770 | Service ticket renewed. | Account, Service Name. | Long-lived sessions. |
| 4771 | Pre-authentication failed (AS-REQ rejected). | Account, Client IP, Failure Code (0x18 = bad password), Pre-Auth Type. | Kerberos brute force. T1110 |
| 4772 / 4773 | TGT request failed / service ticket request failed. | Failure code. | Rarer; enrich 4771/4769 failures. |
| 4768 no pre-auth | TGT requested for an account with Kerberos pre-auth disabled (Pre-Auth Type = 0). | Pre-Authentication Type field. | AS-REP Roasting target — attacker requests these deliberately T1558.004 |
Result / Failure Codes (4768 · 4769 · 4771)
| Code | Meaning | Investigative read |
|---|---|---|
| 0x0 | Success. | — |
| 0x6 | Client not found (username doesn't exist). | User enumeration / typo'd sprays. |
| 0x7 | Server/SPN not found in database. | SPN scanning. |
| 0x9 | Client or server has a null key (KDC_ERR_NULL_KEY). | No key found for the account — usually fixed by resetting its password. Rare; worth a look if seen. |
| 0x12 | Account disabled, expired, locked out, or logon-hours restricted. | Access to a dormant/disabled account being attempted. |
| 0x17 | Password has expired. | — |
| 0x18 | Pre-auth failed — wrong password (account is valid & enabled). | The password-spray / brute-force signature |
| 0x19 | Additional pre-authentication required (KDC_ERR_PREAUTH_REQUIRED). | Normal handshake noise: the client's first AS-REQ has no pre-auth, the KDC asks for it, and the client retries. |
| 0x1F | Integrity check on decrypted field failed. | Possible ticket tampering. |
| 0x20 | Ticket expired (KRB_AP_ERR_TKT_EXPIRED). | Usually normal — clients renew expired tickets. Forged tickets are more often spotted by abnormally long lifetimes (e.g. 10 years) than by this code. |
| 0x25 | Clock skew too great. | Replay tooling / misconfigured attacker host. |
Encryption Types (Ticket Encryption Type field)
| Value | Cipher | Investigative read |
|---|---|---|
| 0x1 / 0x3 | DES-CBC-CRC / DES-CBC-MD5. | Legacy, broken — should never appear on a modern domain |
| 0x11 / 0x12 | AES128 / AES256 (HMAC-SHA1). | Normal for current domains. |
| 0x17 | RC4-HMAC. | Downgrade tell: RC4 on a 4769 where AES is expected = Kerberoasting / overpass-the-hash |
| 0x18 | RC4-HMAC-EXP (export). | Very suspicious — forced weak downgrade. |
| 0xFFFFFFFF | Unknown/None. | Normal in Audit Failure events, where no ticket was issued — not a forgery tell on its own. |
RC4 Hardening — KDC System Events (CVE-2026-20833)
| ID | Meaning | Where | Notes · ATT&CK |
|---|---|---|---|
| System 201–209 | KDCSVC events for RC4 use: warnings 201 (client requests RC4, service account has no msDS-SupportedEncryptionTypes), 202 (service account has no AES keys), 206 and 207; under enforcement these become errors 203, 204, 208 and 209 (RC4 ticket blocked). 205 = DefaultDomainSupportedEncTypes includes weak ciphers (logged at KDC start). | System log on DCs (KDCSVC source) · policy value RC4DefaultDisablementPhase under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters (0 off, 1 audit, 2 enforce) — reported removed by the July 2026 DC updates, leaving enforcement on | KDC default moves to AES; RC4 tickets (0x17) become rarer, so residual RC4 4769s are a stronger Kerberoasting tell. Also expect legacy-app breakage. Sources disagree on April vs July 2026 for the enforcement default — check KB5073381 on your patch level. T1558.003 |
Attack Fingerprints
| Attack | What to look for | Where · ATT&CK |
|---|---|---|
| Kerberoasting | One account requesting many 4769s for different SPNs, RC4 (0x17) encryption, often against service accounts. Offline crack follows. A 4769 for a decoy (honeypot) account's SPN that no real client uses is a near-zero-false-positive signal. | 4769 on DCs — logged only if Audit Kerberos Service Ticket Operations is enabled. AES-encrypted tickets can also be roasted (slower to crack), so RC4-only hunts miss some cases · T1558.003 |
| AS-REP Roasting | 4768 requests for accounts with pre-auth disabled (Pre-Auth Type 0); attacker extracts crackable AS-REP. | 4768 + audit "Do not require Kerberos preauth" flag. T1558.004 |
| Golden Ticket | Forged TGT signed with krbtgt hash. Tells: 4769 with no preceding 4768; anomalous ticket lifetimes; account name not in AD or mismatched RID/SID; sometimes account "MISSING" in logs. | 4769 without 4768 correlation. T1558.001 |
| Silver Ticket | Forged TGS for one service, signed with the service account/computer hash — bypasses the DC entirely, so no 4768/4769 on the DC. Detect at the target service (unexpected 4624 type 3, service-side logs). | DC logs are silent — pivot to the resource host T1558.002 |
| Pass-the-Ticket | Same TGT/TGS reused from a different source IP/host than it was issued to; 4624 type 3 without a matching local auth chain. | Correlate ticket use across hosts. T1550.003 |
| Overpass-the-Hash | NTLM hash used to request a Kerberos TGT — often surfaces as RC4 (0x17) 4768 from a workstation using tooling. | 4768 encryption downgrade + 4648. T1550.002 |
| DCSync | Replication (DRSGetNCChanges) request from a host that is not a Domain Controller. | 4662 from a non-DC account with replication rights GUIDs 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 (Get-Changes) · 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 (Get-Changes-All) · 89e95b76-444d-4c62-991a-0facbeda640c (Filtered-Set). Needs Directory Service Access auditing. T1003.006 |
| DCShadow (rogue DC) | Attacker with Domain Admin rights (or the krbtgt hash) briefly registers a host as a Domain Controller — new server and nTDSDSA objects under CN=Sites,CN=Configuration, plus the Global Catalog (GC/) and DRS (E3514235-4B06-11D1-AB04-00C04FC2DCD2/) SPNs on its computer account — pushes changes (SID History, ACLs, credentials) to a real DC by replication, then unregisters. The pushed change arrives as replication, so the usual 5136 for it can be missing. | 4742 or 5136 adding a GC/ SPN to a non-DC computer; 5137 then 5141 for an nTDSDSA object minutes apart; a DRS SPN left on a non-DC · 4928 / 4929 (replica source naming context established / removed, Audit Detailed Directory Service Replication) · replication RPC (DrsAddEntry, DrsReplicaAdd, GetNCChanges) from a non-DC. Object changes need DS Changes auditing and SACLs; check repadmin /showobjmeta for an originating DSA that is not a known DC. T1207 |
| Diamond / Sapphire Ticket | Variants of Golden Ticket: a legitimately issued TGT is decrypted and modified (diamond) or a real user's PAC is swapped in (sapphire), so 4768 does precede 4769 and the simple "no 4768" test fails. Look for PAC/group mismatches vs AD, privileged SIDs on low-privilege accounts, and anomalous ticket use from odd hosts. | 4768/4769 + PAC validation, AD group state. T1558.001 |
| Kerberos relay via DNS CNAME (CVE-2026-20929) | Authentication relay abusing DNS CNAME records so a client requests a Kerberos ticket for an attacker-chosen service; reported relayed to AD CS to enroll certificates (long-lived persistence). The January 2026 fix is partial: it adds channel binding to HTTP.sys, which blocks relay to AD CS web enrollment, but the CNAME trick still reportedly works against SMB and LDAP, and public proof-of-concept code exists. Detect: certificate issuance (4886/4887) followed by cert-based auth (4768 with PKINIT) for the same account from an unusual host; CNAME records created or changed in internal zones that point at a CA server. | CA logs, DC 4768, DNS. T1557 |
| Certighost (CVE-2026-54121) | AD CS enrollment flaw: any domain user, using a new machine account from the default quota, gets a certificate issued for a Domain Controller's identity, then authenticates as the DC and runs DCSync for the krbtgt secret. Patched July 2026; public proof-of-concept since late July. On the CA, look for 4886 (request received) / 4887 (issued) where the requester is a freshly created machine account, the request attributes cdc: / rmd: point at a host that is not a DC, and the SAN carries a DC's DNS name. Supporting signals: 4741 by a regular user, a certificate-based 4768 for the DC account, and the CA connecting out on 389/445 to a non-DC. | CA Security log — written only if bit 0x04 is set in the CA's AuditFilter; DC 4741 / 4768 / 4662. T1649 · T1003.006 |
| ResetNightmare (CVE-2026-27912) | Validation flaw in the Kerberos Change Password protocol (kpasswd, kadmin/changepw): a user who can write the userPrincipalName of an account they control (or who can create a user/computer, and so owns it) resets the password of any user or computer, Domain Admins included, without knowing the current one. Public proof-of-concept flow: set the controlled account's UPN to the target's sAMAccountName, request a ticket for kadmin/changepw under that name, remove the UPN again, then use the ticket to set the target's new password. Tell: a UPN on a low-privilege account set to another account's sAMAccountName and removed again minutes later, followed by a password change on a privileged account that did not come from its owner, an admin tool or your PAM. | DC Security log: 5136 on userPrincipalName (Value Added then Value Deleted; needs Directory Service Changes auditing + SACL), 4741 / 4720 if the account was newly created. Unpatched DCs only — confirm the CVE-2026-27912 update on every DC, and review who can write userPrincipalName · T1068 · T1098 |
| Shadow Credentials | Attacker with write access to a target's msDS-KeyCredentialLink attribute adds a key credential, then authenticates via PKINIT (certificate) as that account and recovers its NT hash — no password change, so it is quiet. Look for directory changes to the attribute and PKINIT 4768s for accounts that never use certificates. | 5136 (Directory Service Changes) on msDS-KeyCredentialLink by a non-Windows-Hello/non-ADFS writer; 4768 with certificate info. T1556 · T1098 |
| BadSuccessor (dMSA abuse) | Windows Server 2025 delegated Managed Service Accounts: a user who can create a dMSA in any OU (CreateChild) or write to an existing one sets msDS-ManagedAccountPrecededByLink to a privileged account (e.g. a Domain Admin) and msDS-DelegatedMSAState = 2 (migration complete). The KDC then issues the dMSA a TGT carrying the target's privileges and its keys (KERB-DMSA-KEY-PACKAGE). Works with only OU-level create rights — no rights on the target account were needed before the August 2025 fix (CVE-2025-53779); after the fix the link must be paired on both objects, so control of the target is still abusable. | 5137 (dMSA object created) and 5136 on msDS-ManagedAccountPrecededByLink / msDS-DelegatedMSAState — need a SACL on the OUs/dMSA objects; Directory Service log 2946 on the DC = TGT issued to a dMSA; check who holds CreateChild on OUs. T1098 · T1078.002 |
| AdminSDHolder / SDProp abuse | ACE added to the CN=AdminSDHolder,CN=System object; SDProp (about every 60 min) copies it to all protected accounts/groups, giving durable rights that survive manual ACL cleanup. Hunt for unexpected ACEs and accounts with adminCount=1 that are no longer in privileged groups. | 5136 on AdminSDHolder (needs SACL / DS Changes auditing) · T1098 |
| Skeleton Key | Master password injected into lsass on the DC; legit users still work but a universal password also succeeds. RC4 downgrade often forced. | lsass tampering (Sysmon 10), 7045 mimidrv. T1556.001 |
▸ How to read this: directory-object abuse (Shadow Credentials, AdminSDHolder & BadSuccessor)
All three attacks change an object in Active Directory instead of stealing a password, so the first evidence is a directory change on the DC, not a logon.
- Shadow Credentials — write to the target's
msDS-KeyCredentialLink(logged as 5136), then authenticate with a certificate (PKINIT) and a 4768 carrying certificate info. No password changes. - AdminSDHolder / SDProp — an ACE is added to
CN=AdminSDHolder,CN=System(5136, needs SACL / DS Changes auditing); SDProp (about every 60 min) copies it to all protected accounts and groups. - BadSuccessor (dMSA abuse) — on Windows Server 2025, create a delegated Managed Service Account in an OU (5137), then set
msDS-ManagedAccountPrecededByLinkto a privileged account andmsDS-DelegatedMSAState= 2 (5136; both need a SACL). The KDC then issues the dMSA a TGT carrying the target's privileges, recorded as 2946 in the DC's Directory Service log.
Hunt the 5136 first, then the follow-on: PKINIT 4768s for accounts that never use certificates, or adminCount=1 accounts no longer in privileged groups, or a 2946 for a dMSA whose link points at a privileged account. For BadSuccessor, also check who holds CreateChild on OUs.
▸ How to read this: SID History and DCShadow (what is logged, what is not)
SIDs in an account's sIDHistory attribute travel with that account when it logs on, so whoever controls the attribute can give an ordinary user the rights of another SID, such as Domain Admins (RID 512). The same change can reach the directory two ways, and only one of them is logged.
- Written on a DC — the DC records it under Audit User Account Management: 4765 when SID History is added, 4766 when the attempt fails, and the changed value also on 4738. Subject is who did it; Target Account, Source Account and SID List say to whom and what.
- Pushed by a rogue DC (DCShadow) — the attacker registers a host as a Domain Controller (a
GC/SPN on its computer account, 4742 or 5136; annTDSDSAobject, 5137), replicates the change to a real DC, then unregisters (5141). The change arrives as replication, so there is no 4765 and the usual 5136 for it can be missing. Look instead for 4928 / 4929, replication RPC from a non-DC and a DRS SPN left behind.
Because one path is silent, check the directory itself as well: list every account with SID History (Get-ADObject -LDAPFilter "(sIDHistory=*)" -Properties sIDHistory) and use repadmin /showobjmeta to find changes whose originating DSA is not a known DC. An offline edit of ntds.dit also writes no 4765, and the same sweep finds it.
Evidence of Execution
Parse with Eric Zimmerman tools noted per row (all free). Registry user hives: NTUSER.DAT = C:\Users\<user>\NTUSER.DAT, UsrClass.dat = C:\Users\<user>\AppData\Local\Microsoft\Windows\UsrClass.dat.
| Artifact | Location | What it proves | Caveats · Tool |
|---|---|---|---|
| Prefetch | C:\Windows\Prefetch\*.pf | Program executed: run count, last 8 run times (Win8+), files/dirs referenced in first ~10s. Filename embeds path hash — same EXE from two paths = two .pf files. | Disabled by default on Windows Server (Windows 7 also turned it off on some SSDs) · PECmd · Velociraptor Windows.Forensics.Prefetch: use 0.77.3 or later, which reads the volume serial number at offset 16 of the volume entry (earlier releases read offset 12), so re-parse older collections if you pivot on the serial |
| ShimCache (AppCompatCache) | SYSTEM\ControlSet001\Control\Session Manager\AppCompatCache | Executable existed on disk with its path + last-modified time; ordering hints at recency. | Win10+: presence ≠ execution. Written only at shutdown/reboot · AppCompatCacheParser |
| Amcache | C:\Windows\AppCompat\Programs\Amcache.hve | SHA-1 hash of file, full path, size, compile time, first-seen time. Hash even for deleted binaries. | Entry ≠ execution on modern builds · AmcacheParser |
| UserAssist | NTUSER.DAT\...\CurrentVersion\Explorer\UserAssist\{GUID}\Count | GUI-launched programs per user: run count, last run, focus time. Values ROT13-encoded. | CLI launches not recorded · RECmd / Registry Explorer |
| BAM / DAM | SYSTEM\...\Services\bam\State\UserSettings\{SID} | Last execution time of programs per user SID (Background Activity Moderator). | Win10 1709+ · ~1 week retention typical · RECmd |
| SRUM | C:\Windows\System32\sru\SRUDB.dat | Per-app resource usage: CPU seconds, bytes sent/received, owning SID, hourly — 30–60 days. Detects exfil volume by process. | Pair with SOFTWARE hive for interface IDs · SrumECmd |
| Jump Lists | C:\Users\<u>\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations\*.automaticDestinations-ms | App (by AppID) + documents opened with it, MRU timestamps. First/last-used times per item. | JLECmd |
| MUICache | UsrClass.dat\Local Settings\Software\Microsoft\Windows\Shell\MuiCache | Programs run per user (friendly-name cache) — survives even for deleted EXEs. | No timestamps · RECmd |
| RunMRU | NTUSER.DAT\...\Explorer\RunMRU | Commands typed in Start→Run. | Ordered by MRU list, no per-item time · RECmd |
| CapabilityAccessManager | NTUSER.DAT & SOFTWARE\...\CapabilityAccessManager\ConsentStore | Last time apps used mic/camera — anchors interactive sessions. | Win10 1903+ · RECmd |
| Windows Timeline | C:\Users\<u>\AppData\Local\ConnectedDevicesPlatform\*\ActivitiesCache.db | App execution + focus duration, per-hour usage (SQLite). | Removed in newer Win11 builds · WxTCmd |
| Console history (PSReadLine) | C:\Users\<u>\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt | Every interactive PowerShell command typed by the user, plaintext. | No timestamps; attacker may clear |
| PCA (Program Compatibility Assistant) | C:\Windows\appcompat\pca\ · PcaAppLaunchDic.txt · PcaGeneralDb0.txt / PcaGeneralDb1.txt | Windows 11 (22H2+): path + last-launch time for GUI-launched executables, including from removable media and network shares. | Plain pipe-delimited text, no parser needed. One entry per path (latest launch only) — pair with Prefetch/Amcache for run history · T1204.002 |
| Defender protection log (MPLog) | C:\ProgramData\Microsoft\Windows Defender\Support\MPLog-*.log | Plain-text Microsoft Defender troubleshooting log. EstimatedImpact lines name a ProcessImageName with scan Count / TotalTime / MaxTime, which shows the process ran and touched files; detection lines record the file, process and Defender threat name. Often survives when Prefetch, event logs or the binary are gone (string-search for the tool name, e.g. rclone.exe). | Troubleshooting log, not a documented audit trail: entries are not complete and how far back it reaches varies · plain text, grep or a community parser · collect with Defender event logs and Quarantine · T1204.002 |
▸ How to read this: what Defender's MPLog keeps when the usual trail is gone
Microsoft Defender writes a plain-text troubleshooting log, MPLog-*.log, under C:\ProgramData\Microsoft\Windows Defender\Support\. It was not built as evidence, but it often still names a tool after Prefetch, the event logs and the binary itself are gone. Read it in three steps:
- Search it first. String-search the log for the tool name, for example
rclone.exe. It is plain text, so grep or a community parser both work. - Read the two kinds of line. An
EstimatedImpactline names aProcessImageNamewith scan Count / TotalTime / MaxTime: the process ran and touched files. A detection line records the file, the process and the Defender threat name. - Weigh it, then corroborate. It is a troubleshooting log, not a documented audit trail: entries are not complete and how far back it reaches varies, so a missing entry does not prove a tool never ran. Collect it together with the Defender event logs (1116 / 1117) and Quarantine.
File & Folder Knowledge
| Artifact | Location | What it proves | Caveats · Tool |
|---|---|---|---|
| LNK files | C:\Users\<u>\AppData\Roaming\Microsoft\Windows\Recent\*.lnk | File/folder was opened: target path, target MAC times, size, volume serial + type (fixed/removable), hostname. | Auto-created on open; survives target deletion · LECmd |
| Shellbags | UsrClass.dat\Local Settings\Software\Microsoft\Windows\Shell\BagMRU & Bags | Folders browsed in Explorer — including deleted folders, removable media, network shares, zips. | Proves folder knowledge, not file opens · SBECmd / ShellBags Explorer |
| OpenSavePidlMRU | NTUSER.DAT\...\ComDlg32\OpenSavePidlMRU | Files opened/saved via common dialog boxes, per extension. | RECmd |
| LastVisitedPidlMRU | NTUSER.DAT\...\ComDlg32\LastVisitedPidlMRU | Which application used the dialog + last directory per app. | RECmd |
| RecentDocs | NTUSER.DAT\...\Explorer\RecentDocs | Recently opened documents per extension, MRU-ordered. | Key write time = most recent item only · RECmd |
| Office MRU / Reading Locations | NTUSER.DAT\Software\Microsoft\Office\<ver>\<app>\File MRU | Office docs opened with precise timestamps; Reading Locations shows last edit position. | Includes docs on removable/cloud paths · RECmd |
| Trusted Documents | NTUSER.DAT\Software\Microsoft\Office\...\Security\Trusted Documents\TrustRecords | User clicked Enable Editing / Enable Content — macro doc executed | Phishing-origin goldmine · RECmd |
| WordWheelQuery | NTUSER.DAT\...\Explorer\WordWheelQuery | Terms typed into Explorer/Start search — intent evidence. | RECmd |
| Thumbcache | C:\Users\<u>\AppData\Local\Microsoft\Windows\Explorer\thumbcache_*.db | Thumbnails of viewed images/docs — survives original deletion. | Thumbcache Viewer |
| $Recycle.Bin | C:\$Recycle.Bin\<SID>\$I...... / $R...... | $I = original path + deletion time; $R = the content itself. | Shift+Delete bypasses · RBCmd |
| $MFT | Volume root (hidden system file) | Every file's names, sizes, both $STANDARD_INFO and $FILENAME timestamp sets; resident data for tiny files. | Timestomping: compare $SI vs $FN times · MFTECmd |
| $UsnJrnl:$J | \$Extend\$UsnJrnl | Journal of file operations (create, delete, rename) with timestamps — reconstruct deleted tool drops. | Rolls over; hours–days of history · MFTECmd (2026.10.1+: with -f $J -m $MFT, ParentPath is the path the parent folder had when each record was written, so paths through later-deleted or renamed folders resolve) |
| $LogFile | Volume root (hidden NTFS metadata file $LogFile) | NTFS transaction (redo/undo) log: MFT record writes, attribute create/delete, $I30 index entry changes and $STANDARD_INFORMATION timestamp changes with the prior values — can show a rename or timestomp as it happened. | Circular and fixed-size (commonly 64 MB): minutes–hours of history on a busy volume, so collect it early · MFTECmd 2026.10.1+: MFTECmd -f $LogFile -m $MFT --csv <out> (operations grouped by transaction, paths filled in from $MFT) |
| Volume Shadow Copies | System Volume Information | Point-in-time snapshots — recover pre-attack versions of any artifact above. | Ransomware deletes these (vssadmin delete shadows = 4688/Sysmon 1 hit) |
Persistence Locations
Sweep order: autoruns-style registry keys → services → tasks → WMI → per-user shells. Compare against a known-good baseline where possible.
▸ How to read this: persistence by startup stage
The table below lists persistence mechanisms; the diagram groups the same rows by when they fire. Boot items run in system context before anyone logs on. Logon items run as the user when a session starts. Trigger items wait for a schedule, event or a particular program to launch. Use it as a sweep checklist: tick one location per column, then compare against a baseline. Grouping is a rough guide; some mechanisms (e.g. tasks) can also be set to run at boot or logon.
| Mechanism | Location | What to look for | ATT&CK |
|---|---|---|---|
| Run / RunOnce keys | HKLM & HKCU\Software\Microsoft\Windows\CurrentVersion\Run, RunOnce (+ Wow6432Node) | Entries pointing at user-writable paths (AppData, Temp, Public), script hosts, LOLBins. | T1547.001 |
| Services | HKLM\SYSTEM\CurrentControlSet\Services\<name> | ImagePath in odd dirs, svchost -k with rogue ServiceDll, recently created keys. | T1543.003 |
| Scheduled tasks | C:\Windows\System32\Tasks\ (XML) + HKLM\...\Schedule\TaskCache | Tasks with Actions pointing at temp paths; hidden tasks (registry entry without XML or SD value removed). | T1053.005 |
| Startup folders | C:\Users\<u>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup & ProgramData equivalent | Any unexpected LNK/EXE/script. | T1547.001 |
| WMI event subscriptions | C:\Windows\System32\wbem\Repository\OBJECTS.DATA | __EventFilter + CommandLineEventConsumer + __FilterToConsumerBinding triples. Also event 5861. | T1546.003 |
| Winlogon | HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon | Userinit ≠ userinit.exe alone; Shell ≠ explorer.exe. | T1547.004 |
| IFEO debugger | HKLM\...\Image File Execution Options\<exe> | "Debugger" value hijacks launch of the named EXE (classic: sethc.exe → cmd.exe). | T1546.012 |
| Accessibility hijack | C:\Windows\System32\sethc.exe, utilman.exe, osk.exe | Hash mismatch vs known-good / replaced binaries → SYSTEM shell at lock screen. | T1546.008 |
| AppInit_DLLs | HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs | Any DLL listed loads into every user-mode process. | T1546.010 |
| LSA / SSP packages | HKLM\SYSTEM\CurrentControlSet\Control\Lsa (Security Packages, Authentication Packages) | Rogue SSP DLL harvests plaintext creds at logon. | T1547.005 |
| Netsh helper DLLs | HKLM\Software\Microsoft\Netsh | Added helper DLL loads when netsh runs. | T1546.007 |
| COM hijacking | HKCU\Software\Classes\CLSID\{...}\InprocServer32 | Per-user CLSID overriding an HKLM class → DLL loads into trusted processes. | T1546.015 |
| Logon scripts | HKCU\Environment\UserInitMprLogonScript · GPO Scripts\ dirs | Script path set where none expected. | T1037 |
| Print processors / ports | HKLM\SYSTEM\CurrentControlSet\Control\Print\Environments\...\Print Processors | Rogue DLL loaded by spooler as SYSTEM. | T1547.012 |
| Password filter DLL | HKLM\SYSTEM\CurrentControlSet\Control\Lsa → Notification Packages (REG_MULTI_SZ, DLL names without .dll) · the DLL sits in C:\Windows\System32 | LSA asks every registered filter to validate a new password and passes it the plaintext, for local accounts and, on a domain controller, for domain accounts. The default entry is usually only scecli; any other name (bar a known password-policy or sync product) is a lead — hash the DLL and check its signer. Registration takes effect only after LSA restarts (reboot). On a DC a rogue filter captures the plaintext of every password change, including the resets made during remediation. | T1556.002 |
▸ How to read this: what a password filter DLL sees, and how to check one
Windows lets LSA hand every new password to the DLLs listed in one registry value before it accepts the change. A legitimate filter enforces a password policy or syncs passwords; a rogue one simply keeps a copy. Read the figure in three steps:
- What a filter sees. On a password change or reset, LSA asks each DLL in
Notification Packagesto validate the new password and passes it the plaintext: local accounts on any host, domain accounts on a domain controller. - Check the value. Read
HKLM\SYSTEM\CurrentControlSet\Control\Lsa→Notification Packages(REG_MULTI_SZ, names without .dll). The default is usually onlyscecli. Any other name, unless it is a known password-policy or sync product, is a lead: find the DLL inC:\Windows\System32, hash it and check its signer. - Mind the timing. A new filter works only after LSA restarts (a reboot). Check the value on every DC before remediation resets passwords, or a rogue filter captures the new ones too. The same key holds the LSA / SSP packages values, which catch credentials at logon instead.
Anti-Forensics & Timestomping
Core principle: attackers destroy or forge one artifact but rarely all of them. Detection is about cross-checking redundant sources — a wiped log still leaves a 1102/104, a timestomped file still has $FN and $UsnJrnl records.
▸ How to read this: why a timestomp never fully hides
Every NTFS file has a record in the $MFT with two sets of timestamps. Timestomping tools rewrite the $STANDARD_INFORMATION ($SI) set, the one Explorer and most tools show. The $FILE_NAME ($FN) set is updated by the kernel (e.g. on rename/move), so it often keeps the real history. Compare the two, then check sources the attacker did not touch ($UsnJrnl, Prefetch, Amcache, event logs).
Timestomping (Timestamp Manipulation)
| Technique / artifact | Where to check | Detection | Notes · ATT&CK |
|---|---|---|---|
| $SI vs $FN mismatch | $MFT — $STANDARD_INFORMATION vs $FILE_NAME timestamps | Tools usually alter only $SI (user-visible) and not $FN (updated by kernel on rename/move). $SI times inconsistent with $FN = likely timestomp. Double timestomping rewrites $FN too, so confirm with $UsnJrnl. | MFTECmd → compare columns. T1070.006 |
| Sub-second precision loss | $MFT $SI timestamps | $SI times ending in .0000000 (whole seconds) suggest a tool set them; NTFS stores times in 100-nanosecond units. Not proof: files copied from FAT media or extracted from ZIP archives (2-second write times) also end in zeros, and some tools write full precision. | Classic SetMACE / timestomp.exe artifact. |
| $SI created before $FN / impossible times | $MFT | $SI creation time earlier than $FN creation time is the classic backdating sign (tools rewrite $SI only). A file "created" before the OS install date, or modified before created, is logically impossible → forged. | Sanity-check against OS install time. Rule out copied or extracted files that keep their original times. |
| $UsnJrnl still honest | \$Extend\$UsnJrnl:$J | Journal records the real create/rename/write sequence with its own timestamps even after $SI is stomped. | MFTECmd -j. Rolls over in hours–days. |
| Corroborating time sources | Prefetch, Amcache, event logs, LNK, $LogFile, SRUM | The stomped file's real activity window still shows in these independent artifacts. | Build a super-timeline (plaso) and look for the outlier. |
Log Manipulation & Clearing
| Technique | Where to check | Detection | Notes · ATT&CK |
|---|---|---|---|
| Full log clear | Security 1102 · System 104 | The clear event itself names the user and time. Cannot be suppressed while the service runs. | A gap with no 1102 = service was stopped first T1685.005 |
| EventLog service stopped | System 7035/7036/7040 for "Windows Event Log" | Log stopped, events dropped, then restarted — a silent gap without a 1102. | Look for time gaps + service control events. |
| Selective event deletion | .evtx on disk (chunk structure) | Tools (e.g. record-tampering utilities) that remove single records break the chunk CRC / leave record-ID gaps. | Non-sequential EventRecordIDs = tampering · EvtxECmd validates. |
| Audit policy downgrade | Security 4719 · 4907 | Auditing disabled for a subcategory right before activity — explains "missing" events. | T1685.001 |
| Log source redirection / size | HKLM\SYSTEM\...\Services\EventLog\<log> (MaxSize, Retention) | Max size shrunk to force rapid rollover, flushing evidence. | Check MaxSize vs defaults. |
| ETW patching | Sysmon 7 (image loads), memory | In-process EtwEventWrite patch blocks telemetry from that process only — host still logs normally elsewhere. | malfind + compare expected vs actual telemetry. T1685 |
| ATT&CK v19 remapping | Detection rules, reports and tags referencing “Defense Evasion”, T1562.x or T1070.001 / .002 | ATT&CK v19 (2026-04-28) split Defense Evasion into two tactics, Stealth (keeps TA0005) and Defense Impairment (TA0112), and revoked Impair Defenses T1562 into new techniques. Disable or Modify Tools T1685 replaces T1562.001 and T1562.006; its sub-techniques replace Disable Windows Event Logging T1562.002 (→ T1685.001), Cloud Logs T1562.008 (→ T1685.002), Linux Audit System T1562.012 (→ T1685.004), Clear Windows Event Logs T1070.001 (→ T1685.005) and Clear Linux or Mac System Logs T1070.002 (→ T1685.006). Disable or Modify System Firewall T1686 replaces T1562.004 (Windows host firewall T1686.003, cloud firewall T1686.001). ATT&CK v20 (2026-10-13) revoked no further Enterprise techniques, and v20.1 (2026-11-05) revised descriptions and added detection strategies without revoking, deprecating or renaming any. | Rules and reports still tagged T1562.x or T1070.001 / .002 point at revoked IDs — re-tag them. The ATT&CK chips on this sheet use the v19/v20 IDs from v1.16 on. |
Evidence Destruction & Hiding
| Technique | Where to check | Detection | Notes · ATT&CK |
|---|---|---|---|
| Shadow copy deletion | Security 4688 / Sysmon 1 (command line) | vssadmin delete shadows, vssadmin resize shadowstorage, wmic shadowcopy delete, PowerShell deleting Win32_ShadowCopy objects — the command execution is itself logged. Don't rely on VSS service events; process creation is the dependable record. | Near-universal ransomware precursor T1490 |
| USN journal deletion | $UsnJrnl · 4688 for fsutil | fsutil usn deletejournal wipes the change journal; the command still hits process-creation logs. | A suddenly tiny/reset journal is itself suspicious. |
| Prefetch disabled/wiped | HKLM\SYSTEM\...\Memory Management\PrefetchParameters · C:\Windows\Prefetch\ | EnablePrefetcher set to 0, or the folder emptied. Compare .pf count to install age. | Empty Prefetch on a long-lived workstation = suspicious |
| Secure deletion / wiping | $MFT, $UsnJrnl, Amcache, Prefetch | sdelete/cipher usage leaves execution traces; wiped files leave $MFT entries and journal "delete" records even when content is gone. | Look for the wiping tool's own execution evidence. T1070.004 |
| Alternate Data Streams | NTFS ADS: dir /r · Get-Item -Stream * | Payloads hidden in file.txt:hidden.exe. $MFT lists all named $DATA attributes. | MFTECmd surfaces ADS names. T1564.004 |
| Hidden / system attributes | $MFT attribute flags | Files flagged Hidden+System to drop out of default listings; MFT ignores the flags. | T1564.001 |
| File renaming / masquerade | Amcache (hash), $MFT, Sysmon 1 (hashes) | svch0st.exe / lsass.exe in wrong path — hash + signer reveal the true binary regardless of name. | Trust the hash, not the filename T1036 |
| Recycle Bin bypass | $UsnJrnl, VSS | Shift+Delete leaves no $I/$R but the deletion is journaled and prior versions persist in shadow copies. | Recover via VSS. |
| History/artifact clearing | PSReadLine history, browser DBs, ~/.bash_history | Cleared or truncated files — recover deleted SQLite rows (WAL/free pages) and carve from unallocated space. | Empty history + heavy activity elsewhere = red flag. |
| Time/NTP manipulation | System 1 (Kernel-General) time-change · Security 4616 | System clock rolled to skew all subsequent timestamps. | 4616 by a user process (not w32time) = deliberate T1070.006 |
| Registry key backdating | Hive last-write times vs transaction logs | Key timestamps can be forged; the SOFTWARE/SYSTEM .LOG1/.LOG2 transaction logs may retain the true write. | Parse dirty hives with the logs, not just the base hive. |
Network, Browser & USB Activity
| Artifact | Location | What it proves | Caveats · Tool |
|---|---|---|---|
| SRUM network data | C:\Windows\System32\sru\SRUDB.dat (Network Data Usage table) | Bytes sent/received per app per hour — quantify exfiltration by process. | SrumECmd |
| Chrome / Edge history | C:\Users\<u>\AppData\Local\Google\Chrome\User Data\Default\History (Edge: ...\Microsoft\Edge\...) | URLs, visit times, typed vs clicked, downloads with source URL + saved path (SQLite). | Also: Login Data, Web Data, Cookies · Hindsight / DB Browser |
| Firefox history | C:\Users\<u>\AppData\Roaming\Mozilla\Firefox\Profiles\*\places.sqlite | URLs + downloads (moz_annos). | |
| WebCacheV01.dat | C:\Users\<u>\AppData\Local\Microsoft\Windows\WebCache\WebCacheV01.dat | IE/legacy-Edge + Explorer activity — includes file:/// access, so local file opens appear here too. | ESE database · ESEDatabaseView |
| Zone.Identifier (MOTW) | NTFS ADS on downloaded files: file.exe:Zone.Identifier | ZoneId=3 (Internet) + HostUrl / ReferrerUrl = exact download source of the malware. | Stripped by some archivers · MFTECmd sees ADS names. Missing MOTW is not proof a file was made locally — exploited bypasses exist, e.g. CVE-2026-96951 (Windows Shell, October 2026) · T1553.005 |
| Outbound NTLM leak via LNK (CVE-2026-32202) | LNK files containing a remote UNC path in browsed folders · firewall/proxy logs for outbound SMB (TCP 445) or WebDAV | Opening a folder in Explorer that contains a crafted LNK makes Windows resolve its UNC path and send the user's NTLMv2 hash to the attacker's server — the file itself is never clicked. Exploited in the wild (CISA KEV, April 2026); a bypass of the February 2026 fix for CVE-2026-21510. | Any outbound 445 to the internet is a red flag; parse LNKs with LECmd for remote paths · T1187 |
| RDP client MRU | NTUSER.DAT\Software\Microsoft\Terminal Server Client\Servers & Default | Hosts this user RDP'd out to (+ username hint per host) — outbound lateral movement from a compromised box. | RECmd |
| RDP bitmap cache | C:\Users\<u>\AppData\Local\Microsoft\Terminal Server Client\Cache\*.bmc, cache*.bin | Screen-fragment tiles of what the attacker saw over outbound RDP — partial screenshots. | bmc-tools to reassemble |
| User Access Logging (UAL) | C:\Windows\System32\LogFiles\Sum\ — SystemIdentity.mdb · Current.mdb · one {GUID}.mdb per earlier year · transaction logs Svc*.log, Svc.chk, *.jrs | Windows Server (2012+, on by default) record of which authenticated user from which client IP used which server role (role GUID, e.g. File Server) and on which days — inbound access history that outlives rolled-over Security logs, useful for scoping lateral movement to servers. | Server editions only. ESE databases are usually dirty on a live server — collect them with their transaction logs. SumECmd 2026.10.0 (2026-10-18) replays the logs (or repairs) on temporary copies itself, dates Current.mdb by its latest access because that file stores no year (earlier versions used the analyst PC's year), and adds host, domain, hardware, last-boot and time-zone columns to SUMMARY_SystemIdentInfo. Windows only (esent.dll) · KStrike · T1021 |
| Network profiles | SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles | Networks/SSIDs joined with first/last connect times, gateway MAC — geolocate a laptop's movement. | RECmd |
| USB — USBSTOR | SYSTEM\CurrentControlSet\Enum\USBSTOR & USB | Vendor/product/serial of every mass-storage device ever connected. | Correlate serial across hosts · RECmd |
| USB — first/last connect | setupapi.dev.log (C:\Windows\INF\) + Enum key timestamps | First install time (setupapi) and last-connect times (key write times, 0064/0066/0067 properties). | |
| USB — drive letter map | SYSTEM\MountedDevices + SOFTWARE\...\EMDMgmt (pre-Win10) + Windows Portable Devices | Which serial got which letter / volume name — link LNK volume serials to a specific stick. | |
| USB — user linkage | NTUSER.DAT\...\Explorer\MountPoints2\{GUID} | Which user actually used the mounted volume. | GUID matches MountedDevices |
| DNS client cache log | Microsoft-Windows-DNS-Client/Operational (if enabled) · Sysmon 22 | Hostname resolutions by process. | Off by default |
| hosts file | C:\Windows\System32\drivers\etc\hosts | Malicious redirects / AV-update blackholing. |
▸ How to read this: tracing a USB stick from device to user to files
No single key answers "who used this stick, when, and for what". Each artifact answers one question and hands a key to the next:
- USB — USBSTOR: which device. Vendor, product and serial number; the serial is the thread through everything else.
- USB — first/last connect: when. setupapi.dev.log gives the first install time; the Enum key timestamps and the 0064 / 0066 / 0067 properties give the connect times.
- USB — drive letter map: which letter and volume. MountedDevices (plus EMDMgmt before Windows 10 and Windows Portable Devices) ties the serial to a drive letter, volume name and volume GUID.
- USB — user linkage: which user. The MountPoints2 {GUID} in a user's NTUSER.DAT matches the volume GUID in MountedDevices.
Then ask what was opened from it: LNK files record the volume serial and drive type (removable), Shellbags show folders browsed on removable media, and Office MRU and PCA list documents and programs opened from removable paths. Correlate the device serial across hosts to see where else the stick went.
Key Evidence Locations (Collection Checklist)
Minimum triage collection per Windows host — equivalent to a KAPE "SANS triage" style target. Grab hive transaction logs (.LOG1/.LOG2) with every hive.
▸ How to read this: collect the most volatile evidence first
Evidence disappears at different speeds. RAM and live network state change by the second and vanish on shutdown; files on disk persist. So you collect in order of volatility: capture memory first, then network state, running processes, disk triage, and a full image last. The checklist table below is the disk-triage step in detail; its Memory image row (“capture BEFORE shutdown”) is step 1. Record hashes and times as you go for chain of custody.
| Evidence | Path | Contains | Priority |
|---|---|---|---|
| Registry hives (system) | C:\Windows\System32\config\SYSTEM, SOFTWARE, SAM, SECURITY, DEFAULT (+ .LOG1/.LOG2) | Services, ShimCache, USB, network profiles, local accounts (SAM), LSA secrets (SECURITY). | P1 |
| Registry hives (user) | C:\Users\*\NTUSER.DAT · C:\Users\*\AppData\Local\Microsoft\Windows\UsrClass.dat | UserAssist, RecentDocs, RunMRU, shellbags, MuiCache. | P1 |
| Event logs | C:\Windows\System32\winevt\Logs\*.evtx | Everything in the Event ID section. Security, System, PowerShell, Sysmon, TS-*, WMI, Defender first. | P1 |
| Filesystem metadata | $MFT · $LogFile · $Extend\$UsnJrnl:$J · directory $I30 index slack | Full file timeline incl. deleted files. | P1 |
| Prefetch + Amcache | C:\Windows\Prefetch\ · C:\Windows\AppCompat\Programs\Amcache.hve | Execution evidence + hashes. | P1 |
| User artifacts | C:\Users\*\AppData\Roaming\Microsoft\Windows\Recent\ (LNK + Jump Lists) · PSReadLine history | File knowledge + typed commands. | P1 |
| SRUM | C:\Windows\System32\sru\SRUDB.dat | App + network usage history. | P2 |
| Scheduled tasks + WMI repo | C:\Windows\System32\Tasks\ · C:\Windows\System32\wbem\Repository\ | Persistence configs. | P2 |
| Browser data | Per-browser paths above | Delivery vector, exfil to web services. | P2 |
| Memory image | Live capture (winpmem / Magnet RAM Capture / DumpIt) · hiberfil.sys · pagefile.sys · MEMORY.DMP | Injected code, C2 sockets, keys, console history — capture BEFORE shutdown. | P1 (live) |
| Shadow copies | System Volume Information (vssadmin list shadows) | Historical versions of all of the above. | P3 |
| Recycle Bin | C:\$Recycle.Bin\ | Deleted-but-recoverable content + deletion times. | P2 |
Memory Forensics (Volatility 3)
Syntax: vol -f image.mem <plugin>. Prefix windows. shown; linux. equivalents exist for most.
▸ How to read this: a typical Volatility 3 triage order
Start with the process view, then follow what looks wrong into the other plugins:
- pslist / pstree for the tree, then psscan — a process present in psscan but missing from pslist is unlinked or terminated.
- cmdline and netscan for what each suspicious process ran and talked to.
- malfind, dlllist / ldrmodules and handles for injection, unlinked DLLs and lsass access.
- filescan + dumpfiles to extract what you found; hashdump / lsadump / cachedump to scope credential exposure.
At any step, compare memory with the disk: svcscan finds memory-only service records, registry.hivelist / printkey shows Run keys as they were at capture time, and modscan / driverirp finds rootkit drivers.
| Plugin | Purpose | Hunt notes |
|---|---|---|
| windows.pslist / pstree | Running processes / parent-child tree. | Wrong parent (e.g. lsass not under wininit), misspelled system names, user-writable paths. |
| windows.psscan | Scan for process objects incl. terminated/hidden. | Diff vs pslist → unlinked (rootkit-hidden) processes. |
| windows.cmdline | Command line per process. | Encoded PowerShell, rundll32/regsvr32 arguments. |
| windows.netscan / netstat | TCP/UDP endpoints with owning process. | ESTABLISHED to external IPs from Office apps, svchost on odd ports. |
| windows.malfind | Injected / RWX private memory regions. | MZ headers or shellcode in unmapped regions; dump with --dump. |
| windows.dlllist / ldrmodules | Loaded modules / loader-list discrepancies. | ldrmodules False,False,False rows = unlinked DLLs. |
| windows.handles | Open handles per process. | Handles to lsass.exe from non-system processes = cred dumping |
| windows.filescan + dumpfiles | File objects in memory; extract them. | Recover attacker tools never written stably to disk. |
| windows.registry.hivelist / printkey | Hives in memory; read keys live. | Run keys as they were at capture time. |
| windows.hashdump / lsadump / cachedump | SAM hashes, LSA secrets, cached domain creds. | Scope of credential exposure. |
| windows.svcscan | Services from memory. | Compare to registry — memory-only service records. |
| windows.modscan / driverirp | Kernel modules / hooked IRPs. | Rootkit drivers. |
Linux Triage
▸ How to read this: Linux persistence by boot, login and trigger
The table below is ordered by artifact; the diagram groups the same rows by when the attacker's code runs. Boot items start with the system (units, kernel modules, a preloaded library, legacy RC scripts). Login / shell items run when a user session, shell or authentication happens. Trigger items wait for a schedule or a device event. Standing access items are not launchers: they are accounts, setuid binaries or sudo rules left behind so the attacker can return with privilege. Sweep one row per column, then compare file mtimes and package ownership. Grouping is a rough guide; a systemd unit can also be started by a timer.
RC scripts (rc.local / init.d) are the legacy boot launcher. On systemd before v260 an executable /etc/rc.local runs through rc-local.service and /etc/init.d scripts get generated units; systemd v260 removed both, so check systemctl --version before calling a script live, and check package ownership either way.
XDG autostart entries are the login launcher of Linux desktops: a .desktop file in ~/.config/autostart (one user) or /etc/xdg/autostart (every user) runs its Exec= command each time the graphical session starts, so they matter only on hosts with a desktop login. A user file with the same name overrides the system-wide one. Read the Exec= line and check package ownership of the system-wide entries.
The chips under the diagram open the row behind each box.
| Artifact | Location / command | What it proves | Caveats |
|---|---|---|---|
| Auth logs | /var/log/auth.log (Debian) · /var/log/secure (RHEL) | SSH logons (accepted/failed, key vs password, source IP), sudo usage, user/group changes. | journalctl on systemd-only distros: journalctl _COMM=sshd |
| wtmp / btmp / lastlog | /var/log/wtmp · btmp · lastlog → last, lastb, lastlog · on newer distros SQLite DBs: /var/lib/wtmpdb/wtmp.db (or /var/log/wtmp.db) → wtmpdb last · /var/lib/lastlog/lastlog2.db → lastlog2 | Login history / failed logins / last login per account. | Binary; attackers wipe with utmpdump tricks — check for nulled records. openSUSE replaced the old files with wtmpdb/lastlog2 (Y2038) and Fedora is migrating; old files may remain but stop updating. |
| Shell history | ~/.bash_history · ~/.zsh_history | Commands run per user. | No timestamps unless HISTTIMEFORMAT set; trivially cleared — recover from memory/disk carving |
| Cron | /etc/crontab · /etc/cron.d/ · /etc/cron.{hourly,daily} · /var/spool/cron/crontabs/ | Scheduled persistence. | Check every user's spool incl. system accounts · T1053.003 |
| Systemd units & timers | /etc/systemd/system/ · ~/.config/systemd/user/ · systemctl list-timers --all | Service/timer persistence. | Compare unit ExecStart against package-owned files (dpkg -V / rpm -Va) · T1543.002 |
| RC scripts (rc.local / init.d) | /etc/rc.local · /etc/init.d/* · systemctl status rc-local.service · systemctl --version | Legacy boot-time persistence: shell commands or a binary path that run as root at every boot. | On systemd before v260, an executable /etc/rc.local is run by rc-local.service and /etc/init.d scripts get generated units (systemd-sysv-generator); systemd v260 removed both, so check the systemd version before calling a script live. An rc.local or init.d script not owned by a package (dpkg -S / rpm -qf) = suspicious · T1037.004 |
| SSH keys | ~/.ssh/authorized_keys (all users, incl. root) | Backdoor keys = passwordless persistence. | Check file mtimes; also /etc/ssh/sshd_config for AuthorizedKeysFile overrides · T1098.004 |
| Shell rc files | ~/.bashrc · ~/.bash_profile · /etc/profile.d/* | Aliased commands, injected loaders on every shell. | T1546.004 |
| XDG autostart entries | ~/.config/autostart/*.desktop · /etc/xdg/autostart/*.desktop (read the Exec= line) | Persistence on Linux desktops: the command in Exec= runs every time the user's graphical session (GNOME, KDE, XFCE …) starts. A user file with the same name as a system-wide one overrides it. | Only fires on systems with a desktop login, not headless servers; malware names the file after a real app. A .desktop entry whose Exec= points into a hidden or temp directory, or a system-wide entry not owned by a package (dpkg -S / rpm -qf) = suspicious · T1547.013 |
| ld.so.preload | /etc/ld.so.preload · LD_PRELOAD in env/units | Userland rootkit — library injected into every dynamic binary | File should normally not exist · T1574.006 |
| PAM | /etc/pam.d/* · /lib/security/ modules | Backdoored auth (accept any password, log creds). | Hash-compare pam_unix.so vs package · T1556.003 |
| /proc for live response | /proc/<pid>/exe, cwd, cmdline, environ, maps · /proc/net/tcp | exe → "(deleted)" = running binary removed from disk; recover via cp /proc/PID/exe. | Volatile — collect before shutdown |
| Audit log | /var/log/audit/audit.log → ausearch -i | Syscall-level trail (execve, connect) if auditd rules configured. | Ruleset-dependent. Rules flushed (auditctl -D) or auditd stopped just before activity = tampering · T1685.004 |
| Package logs | /var/log/dpkg.log · /var/log/yum.log · dnf history | Software installed/removed and when. | |
| Web server logs | /var/log/apache2/ · /var/log/nginx/ | Webshell access patterns: POSTs to odd .php, repeated single-URI hits, long query strings. | Correlate with file mtimes in webroot · T1505.003 |
| Journal (persistent) | /var/log/journal/ · journalctl -o json --since ... · journalctl --list-boots | Unified service/auth/kernel log on systemd hosts; survives only if Storage=persistent (else /run/log/journal is volatile). | Binary — collect the directory, not just text exports. Attackers use journalctl --vacuum-* · T1685.006 |
| Temp & staging dirs | /tmp · /var/tmp · /dev/shm | Dropped tools, miners, memfd artifacts — exec from these dirs is a red flag. | noexec mounts reduce noise |
| udev rules | /etc/udev/rules.d/ · /lib/udev/rules.d/ (look for RUN+= entries) · udevadm test | Persistence that fires on a device event rather than boot/login — the sedexp Linux malware (Aon Stroz Friedberg, 2024) hid its trigger in a udev rule matching /dev/random. | Diff rules vs package-owned files; rarely monitored, so few alerts exist · T1546.017 |
| Sysmon for Linux (eBPF) | Syslog/journal (Sysmon XML events) · sysmon -c config.xml · EbpfEvent (reported for Sysmon for Linux 1.5; event name not confirmed in release notes) | Process, network and file events with full command line on Linux hosts; EbpfEvent records eBPF program loads — a rootkit/stealth-tooling indicator. | Only present if deployed beforehand; separate product from Windows Sysmon (Sysinternals Suite 2026-04/2026-07 notes list 1.5 / 1.5.2) · T1014 |
| at / batch jobs | /var/spool/cron/atjobs/ (Debian) · /var/spool/at/ (RHEL) · atq · at -c <job> | One-shot scheduled execution; job file holds the full environment and command, owner and run time. | Easy to miss when only cron is reviewed; spool files vanish after the job runs · T1053.002 |
| known_hosts & ssh client config | ~/.ssh/known_hosts · ~/.ssh/config · ~/.ssh/id_* (key mtimes) | Hosts this account has SSH'd to — outbound lateral-movement trail from a compromised host. Hashed entries (HashKnownHosts) can be verified against candidate hostnames with ssh-keygen -F host. | Only records first connection per host · T1021.004 |
| sudoers drop-ins | /etc/sudoers · /etc/sudoers.d/* · visudo -c | Added NOPASSWD entries = privilege-escalation persistence; sudo executions are logged in auth.log/secure/journal. | Check file mtimes; package-owned files only for stock distro entries · T1548.003 |
| Loaded kernel modules | lsmod · /proc/modules · /sys/module/ · dmesg | grep -i taint · /lib/modules/$(uname -r)/ | Kernel-level rootkits appear as unexpected or hidden modules; an "out-of-tree" or "unsigned module" taint message is written when a module loads. | Hidden modules vanish from lsmod — compare /sys/module and memory analysis · T1547.006 T1014 |
| Loaded eBPF programs | bpftool prog list · bpftool prog show id <N> · bpftool map list · bpftool link list · /sys/fs/bpf/ (pinned objects) | eBPF rootkits (VoidLink, LinkPro, Atomic Arch — Datadog, July 2026) hide ports from ss, truncate bpftool listings or kill debuggers; programs using the helpers bpf_probe_write_user, bpf_override_return or bpf_send_signal are strong tells. | bpftool prog list can be truncated by a rootkit — query IDs one by one with prog show id and trust load-time telemetry (BPF_PROG_LOAD) over live listings · interactive bpftool use outside observability/networking agents is suspicious · T1014 |
| Local accounts & UID 0 | /etc/passwd · /etc/shadow · /etc/group · awk -F: '$3==0' /etc/passwd | Added local accounts, extra UID 0 users and accounts with a login shell that should not have one; shadow shows password-set state. | Only root should have UID 0; check mtimes on passwd/shadow and correlate with useradd/usermod lines in auth.log/secure · T1136.001 |
| SUID / SGID binaries | find / -xdev -type f \( -perm -4000 -o -perm -2000 \) -ls · verify with rpm -Va / debsums -c | Setuid-root copies of shells, editors or interpreters left behind give an instant root path for a returning attacker. | A SUID file outside package-owned paths, or in /tmp, /dev/shm or a home directory, is a strong persistence/privilege-escalation tell · compare against the package database and a known-good baseline · T1548.001 |
| Package manager hooks & maintainer scripts | /etc/apt/apt.conf.d/* (APT::Update::Pre-Invoke, DPkg::Pre-Invoke / DPkg::Post-Invoke) · /etc/dnf/plugins/ · /var/lib/dpkg/info/*.postinst · grep -rn Invoke /etc/apt/apt.conf.d/ · rpm -qa --scripts | Persistence and root execution triggered by package activity: APT runs each command in a Pre-/Post-Invoke list through /bin/sh as root when it updates indexes or calls dpkg, and dpkg / rpm maintainer scripts (preinst, postinst, prerm, postrm) run as root whenever a package is installed, upgraded or removed. | Config management (Puppet, Chef, Ansible) and vendor agents also drop apt.conf.d files, so check ownership with dpkg -S / rpm -qf. An Invoke entry that calls curl, wget, a shell one-liner or a script in a temp or hidden directory, or dpkg -i of a local .deb from an unusual parent process = suspicious · T1546.016 |
| MOTD scripts (update-motd.d) | /etc/update-motd.d/* (Debian / Ubuntu) · output cached in /run/motd.dynamic (/etc/motd is usually a symlink to it) · ls -la --time-style=full-iso /etc/update-motd.d/ · dpkg -S /etc/update-motd.d/* | Logon-triggered root execution: pam_motd runs every executable script in the directory as root (via run-parts, in name order) at each SSH or console login, so a dropped script fires whenever anyone logs in. Scripts are expected to be NN-name files that only print text. | Packages, cloud images and container runtimes add legitimate scripts, so check ownership with dpkg -S and compare against the package. A script no package owns, or one that calls curl, wget, nc, a reverse shell or a file in a temp or hidden directory, or a MOTD run that spawns network tools or shells = persistence · T1037 |
▸ How to read this: what package activity runs as root, and how to tell a hook from a backdoor
Package managers run other people's code as root by design, which makes their hooks a quiet place to persist: nothing new starts at boot, the command simply runs the next time someone updates or installs software. Two mechanisms matter. Read them in four steps:
- Know what fires. APT reads every file in
/etc/apt/apt.conf.d/: commands inAPT::Update::Pre-Invokerun when package indexes are updated, and commands inDPkg::Pre-Invoke/DPkg::Post-Invokerun around each dpkg call, each one through /bin/sh as root. Separately, dpkg and rpm maintainer scripts (preinst, postinst, prerm, postrm) run as root whenever their package is installed, upgraded or removed. On dnf hosts, also sweep/etc/dnf/plugins/. - Sweep, then check who owns each file.
grep -rn Invoke /etc/apt/apt.conf.d/lists the APT hooks andrpm -qa --scriptsprints the rpm scriptlets. Askdpkg -S/rpm -qfwhich package owns each hook file. Files from a package, from your config management (Puppet, Chef, Ansible) or from a vendor agent are the usual false positive. - Read the command. An Invoke entry that calls curl or wget, a shell one-liner, or a script in a temp or hidden directory is suspicious, and so is
dpkg -iof a local .deb started by an unusual parent process (T1546.016). - Time it. Package logs show when packages were installed or removed, that is, when the hooks had a chance to run; Temp & staging dirs hold the scripts a hook calls; the Audit log has the execve trail if auditd rules cover it.
▸ How to read this: a login runs the MOTD scripts as root, so a dropped script fires for every user
The message of the day on Debian and Ubuntu is not a static file: it is built by scripts, and those scripts run as root at every SSH or console login, whoever logs in. That makes /etc/update-motd.d/ a login-triggered persistence point that needs no change to any user's shell files (T1037). Read it in four steps:
- Know what fires. At login, pam_motd runs every executable script in
/etc/update-motd.d/through run-parts, in name order, as root, and caches the output in/run/motd.dynamic(/etc/motdis usually a symlink to it). A legitimate script is anNN-namefile that only prints text. - Sweep, then check who owns each script.
ls -la --time-style=full-iso /etc/update-motd.d/gives the scripts and their full timestamps;dpkg -S /etc/update-motd.d/*names the package behind each one. Packages, cloud images and container runtimes add legitimate scripts, so compare an owned script with its package; a script no package owns needs reading. - Read the script. One that calls curl, wget or nc, opens a reverse shell or runs a file from a temp or hidden directory is persistence, and so is a MOTD run that spawns network tools or shells.
- Time it. Every login in the Auth logs is one more run of every script; Temp & staging dirs hold the files a script calls; the Audit log has the execve trail if auditd rules cover it.
macOS Triage
Starter set — grows each week. Many paths need Full Disk Access / root and are protected by SIP/TCC; collect from a mounted image where possible.
▸ How to read this: macOS artifacts by the question they answer
The table below is a flat list; the map groups the same rows by the question each one answers. Most incidents start with something arriving: Quarantine events and Safari history give the source URL and the time a file came in. Use that time as the pivot and walk the other groups.
- Ran or used: the Unified log for process, auth, sudo and TCC activity; KnowledgeC.db and Biome streams for app usage and pattern of life (newer macOS moves data to Biome, so check both).
- Typed: zsh history and Terminal's per-session files, which can hold commands the main history file lacks.
- Files touched: FSEvents keeps create, modify, delete and rename history even after deletion, but without the process; recent items list documents, apps and servers a user opened, and .DS_Store lists names Finder has seen, which proves browsing, not execution. The Spotlight store indexes paths, timestamps and document attributes per volume; its entries can outlive the file, and a store on removable media can list the paths that were on it. Corroborate it with FSEvents.
- Stays: launchd plists and the background task database (
sfltool dumpbtm); compare one against the other. Also check the legacy launchers, Login / logout hooks & StartupItems: deprecated, but they still run as root. - Allowed: TCC.db shows current grants; the tccd lines in the unified log show the moment of a grant, even if the database row is later removed.
Collect live first: Info/Debug unified-log messages and tccd entries age out quickly, and Safari's database needs its -wal file. Many of these paths need Full Disk Access.
| Artifact | Location / command | What it proves | Caveats · ATT&CK |
|---|---|---|---|
| Unified log | /var/db/diagnostics/ + /var/db/uuidtext/ · log show --archive · log collect | System-wide process, auth, sudo, XProtect and TCC activity. | Binary tracev3; short retention for Info/Debug messages. Collect early. |
| TCC.db | /Library/Application Support/com.apple.TCC/TCC.db (system) · ~/Library/Application Support/com.apple.TCC/TCC.db (user) | Which apps were granted camera, mic, Full Disk Access, screen recording, etc., and when. | Permission changes also appear in unified log (subsystem com.apple.TCC). TCC abuse/edits = defense evasion · T1548.006 |
| KnowledgeC.db | /private/var/db/CoreDuet/Knowledge/knowledgeC.db (system) · ~/Library/Application Support/Knowledge/knowledgeC.db (user) | App usage, focus, device lock/unlock, web usage — pattern-of-life timeline. | SQLite; Apple is moving data to Biome on newer macOS, so cover both. |
| Biome streams | ~/Library/Biome/streams/ (e.g. restricted/App.MenuItem/local on macOS 26 Tahoe) · parse with ccl-segb | Per-user behavioral streams. Unit 42 (2026) documented App.MenuItem: the text of each menu item a user selects (e.g. Compress, Move to Trash, Empty Trash) with a timestamp. | SEGB container wrapping protobuf records; Unit 42 found common commercial forensic suites did not parse App.MenuItem. The stream names the action, not always its target — correlate with FSEvents. Format undocumented — validate results. |
| FSEvents | /System/Volumes/Data/.fseventsd/ (volume root /.fseventsd/) | File create/modify/delete/rename history per volume, even after deletion. | Gzip-compressed pages; no per-process attribution. |
| Spotlight store | /System/Volumes/Data/.Spotlight-V100/Store-V2/<UUID>/ (store.db + .store.db; volume root /.Spotlight-V100/ on external media) · ~/Library/Metadata/CoreSpotlight/index.spotlightV3/ (per-user, 10.13+) · spotlight_parser.py <store.db> <out_dir> or mac_apt | Per-volume metadata index of indexed files: paths, timestamps and document attributes; a store on removable media can show the volume name and the paths that were on it. | Index entries can outlive the file they describe. Undocumented binary format; volumes excluded from indexing or an index rebuilt with mdutil -E leave little or nothing. Collect the whole Store-V2 and per-user folders; corroborate with FSEvents. |
| LaunchAgents / LaunchDaemons | ~/Library/LaunchAgents · /Library/LaunchAgents · /Library/LaunchDaemons · /System/Library/ (signed OS) | plist-based persistence (RunAtLoad, KeepAlive, ProgramArguments). | Check plist mtimes and ProgramArguments paths in /tmp, ~/Library · T1543.001 / T1543.004 |
| Login items / BTM | System Settings > Login Items · sfltool dumpbtm | Background task management database of registered login items/agents. | Shows items added via SMAppService; compare with plists above · T1547.015 |
| Login / logout hooks & StartupItems | /Library/Preferences/com.apple.loginwindow.plist (LoginHook / LogoutHook keys) · defaults read /Library/Preferences/com.apple.loginwindow · /Library/StartupItems/*/StartupParameters.plist | Legacy persistence: a login or logout hook runs its script as root at each user logon / logoff; a StartupItem runs as root late in boot. | Both deprecated in favour of LaunchDaemons / LaunchAgents (login hooks since OS X 10.11); only one login and one logout hook per system; admin rights needed to set them. Any LoginHook / LogoutHook value or populated StartupItems folder on a current Mac = review the script · T1037.002 / T1037.005 |
| Quarantine events | ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2 | Files downloaded/received with source URL and app. | SQLite; entries persist after file deletion. |
| TCC changes in unified log | log show --predicate 'process == "tccd"' · message text "Update Access Record" | Moment an app was granted or denied a TCC permission, even if the TCC.db row is later removed. | Entries can be volatile (not persisted) and age out within minutes — collect live early · T1548.006 |
| zsh sessions & history | ~/.zsh_history · ~/.zsh_sessions/ (per-terminal-session history) · ~/.bash_history | Terminal commands per user; zsh is the default shell since macOS Catalina, and Terminal's per-session files can retain commands the main history file lacks. | Timestamps only with EXTENDED_HISTORY; easily cleared · T1059.004 |
| Recent items (sharedfilelist) | ~/Library/Application Support/com.apple.sharedfilelist/ (*.sfl2 / *.sfl3) · ~/Library/Preferences/com.apple.recentitems.plist (older) | Recently opened documents, applications and servers per user (including mounted network volumes). | Files are NSKeyedArchiver plists/bookmarks; format varies by macOS version · T1083 |
| Install history | /Library/Receipts/InstallHistory.plist | Timeline of package/software installs and OS updates with installing process. | Does not cover drag-and-drop app copies or Homebrew; pair with Quarantine events and FSEvents. |
| .DS_Store | .DS_Store in each Finder-browsed folder (volume and user dirs, incl. external media and network shares) | Lists filenames Finder has seen in the folder, even after those files were deleted. | Proves presence/browsing of names, not execution or content; parse with a DS_Store parser. |
| Safari history | ~/Library/Safari/History.db (SQLite: history_items + history_visits) · ~/Library/Safari/Downloads.plist | Visited URLs with visit count, page title and per-visit time; redirect chains (redirect_source / redirect_destination) show where a phishing or download link really led. Downloads.plist lists downloaded files with their source URL and save path. | visit_time is Mac absolute time (seconds since 2001-01-01 UTC); copy the -wal file with the database. Protected by TCC — needs Full Disk Access on a live system. |
| XProtect Behavior Service (XPdb) | /var/protected/xprotect/XPdb (SQLite) · sudo sqlite3 /var/protected/xprotect/XPdb "select * from events;" | Behaviour detections recorded by Apple's built-in XProtect Behavior Service: violated_rule (e.g. macOS.Browser.Generic = a process touched browser data), exec_path, exec_cdhash, exec_signing_id, exec_team_id, exec_sha256, exec_is_notarized and a timestamp — names and hashes the offending binary even after it is deleted. | Logs only, does not block; rules (Bastion rules in XProtect.app) are updated silently and the schema is undocumented by Apple, so verify columns on the target build. Needs root · T1555.003 T1539 |
Containers, Kubernetes & ESXi
Containers are ephemeral — deleting a pod removes its writable layer but not node, control-plane and network traces. Preserve first (cordon the node, snapshot), then collect.
▸ How to read this: container evidence by layer
A container incident leaves evidence on four layers, and each one disappears on a different trigger. The pod holds the writable layer (Docker overlay diff) and the mounted service account token, and loses the writable layer when the container is removed. The node holds runtime state and kubelet / containerd logs until rotation or node recycle. The control plane holds the K8s API audit log, but only if audit logging was enabled. The cloud holds API logs for any cloud identity mapped to a service account.
- Preserve from the top down: cordon the node and copy the writable layer and node logs before the pod or node is recycled.
- Follow the attacker's reach down the stack: a privileged, hostPID or hostPath pod reaches the node; a stolen service account token reaches the API server; IRSA / Workload Identity carries that identity into the cloud.
- Check the audit trail before scoping: a token minted with TokenRequest leaves no file on disk, so the API audit log is the only witness.
ESXi is a separate stack. Its logs live on a ramdisk and are lost on reboot unless remote syslog or persistent scratch is configured. For the same reason most system files do not survive a reboot, which makes ESXi boot persistence (local.sh) one of the few places a backdoor can live: diff it against a clean host of the same build.
| Artifact | Location / command | What it proves | Caveats · ATT&CK |
|---|---|---|---|
| K8s API audit log | API server audit backend (file / webhook) · JSON lines | Who (user, SA), verb, resource, namespace, response — exec, secrets get, create pods, RBAC changes. | Off by default on many self-managed clusters; managed K8s exports to cloud logging. T1609 / T1552.007 |
| Container runtime state | crictl ps -a · crictl inspect · /var/lib/containerd · /var/lib/docker · /var/log/pods · /var/log/containers | Images, layers, mounts, container logs, start/exit times. | Log rotation is aggressive; copy before node recycle. |
| Docker overlay diff | docker diff <ctr> · docker export · overlay2 upperdir | Files added/changed in the writable layer since image start. | Lost when container is removed. Compare against image digest. |
| Kubelet / node logs | journalctl -u kubelet · -u containerd · host auth.log | Pod scheduling, image pulls, node-level SSH access. | Pivot from pod to node via hostPath / privileged mounts · T1611 |
| ESXi logs | /var/log/shell.log · /var/log/auth.log · /var/log/hostd.log · /var/log/vmkernel.log | Shell/SSH enablement, commands run, logins, VM power/datastore ops. | Ransomware often targets ESXi; logs live in a ramdisk and are lost on reboot unless remote syslog or persistent scratch is set — check esxcli system syslog config get and preserve before reboot. shell.log records commands before variable expansion — obfuscated commands appear unreadable. Quick ESXi Log Parser (QELP) builds CSV timelines. T1486 / T1059.004 |
| ESXi audit records | esxcli system auditrecords get · esxcli system auditrecords local set (ESXi 7.0 U2+) · /var/run/log/ or configured audit dir | Records shell/CLI commands and admin actions when audit record storage is enabled. | Attackers may disable or clear it; forward to syslog · T1685 |
| ESXi shell command obfuscation | /var/log/shell.log · forwarded syslog | CrowdStrike catalogued 21 obfuscation methods (escape sequences, character generation, Unicode injection, crypto/alternative encodings, infrastructure-keyed payloads) tested on ESX 7.0.3; secondary reports say shell logging records commands at parse time, before variable expansion. | Logged commands may be unreadable strings, so keyword hunts miss them — hunt for obfuscation syntax patterns instead and verify effects (syslog config, SSH state, VM power state) · T1027, T1685 |
| ESXi VM inventory | /etc/vmware/hostd/vmInventory.xml · .vmx / .vmdk paths · ransom note in web UI docroot | Lists registered VMs; ESXi-targeting ransomware (reported for Payload, 2026) reads it to find .vmx/.vmdk targets before encrypting. | Unexpected reads, unusual VMDK I/O, and VMs powered off just before encryption are tells. Logs may be on RAM disk — collect early · T1486 |
| ESXi boot persistence (local.sh) | /etc/rc.local.d/local.sh · /var/spool/cron/crontabs/root | Most ESXi system files live in memory and are discarded at shutdown, so local.sh is one of the few places a backdoor survives a reboot; it runs as root at startup. | Diff local.sh against a clean host of the same build. Cron jobs on ESXi are created by writing the root crontab file directly, so check it too. Unknown commands in local.sh = persistent backdoor · T1037.004 / T1053.003 |
| Managed-K8s audit logging | EKS: cluster logging types incl. audit · AKS/GKE: diagnostic settings / Cloud Audit Logs — verify enabled | Control-plane audit trail (exec, secret reads, RBAC changes) is often not collected by default in managed services; if off, the only traces are kubelet/node logs and cloud-side API logs. | Kubelet logs survive pod deletion only until rotation — collect node logs early. Check retention before scoping. |
| Privileged pods & host-namespace escape | K8s audit log: create pods with securityContext.privileged, hostPID, hostNetwork or hostPath volumes · node: nsenter -t 1 in process/audit logs | Pod specs that share the host's namespaces or mount the host filesystem are the usual container-to-node escape route (nsenter into PID 1, chroot into a hostPath mount). | Workload pods with privileged/hostPID/hostPath outside system namespaces are a red flag · Pod Security Admission or admission-policy logs show whether this was permitted · T1611 |
| Service account token theft | In pod: /var/run/secrets/kubernetes.io/serviceaccount/ (token, ca.crt, namespace) · K8s audit log: create on serviceaccounts/token (TokenRequest) · get secrets of type service-account-token | Reads of the mounted token, or tokens minted via TokenRequest for a more privileged service account, let an attacker act as that identity against the API server and, through IRSA / Workload Identity, against the cloud. | A minted token leaves no file on disk — only the API audit log shows it, and Elastic deprecated its GKE TokenRequest rule (Aug 2026) because GKE Cloud Audit Logs do not export that subresource. Check which (Cluster)RoleBinding grants create serviceaccounts/token · T1528 T1552.007 |
| Pod exec / attach / port-forward | K8s audit log: objectRef.resource=pods with objectRef.subresource exec / attach / portforward · verb create (SPDY, HTTP POST) or get (WebSocket, HTTP GET; default from kubectl 1.31) · user.username · sourceIPs · requestURI (holds the command= arguments) | Who opened a shell or ran a command in which pod and namespace, and from where. The requestURI carries the command that was run; nothing typed inside an interactive shell is logged. | An exec by a human user or an unexpected service account into a production pod is hands-on-keyboard activity · the audit policy must log the pods/exec subresource at Metadata or Request level, and hunts must match both verbs or they miss WebSocket clients · T1609 |
| RBAC role binding changes | K8s audit log: objectRef.apiGroup=rbac.authorization.k8s.io · objectRef.resource clusterrolebindings / rolebindings · verb create / update / patch · requestObject.roleRef.name · requestObject.subjects · user.username | Who granted which subject (user, group or service account) which role, cluster-wide or in one namespace. Binding cluster-admin to a namespace service account, or any role to system:anonymous / system:unauthenticated, hands that identity the cluster. | A new cluster-admin binding made by a service account or an unexpected user is privilege escalation and persistence · then hunt the subject's next actions (secret reads, privileged pods, DaemonSets) · bootstrap, upgrade and recovery tooling re-creating bindings is the usual false positive · T1098.006 |
| CronJob / DaemonSet persistence | K8s audit log: verb create/patch on cronjobs (batch) or daemonsets (apps) · kubectl get cronjobs,daemonsets -A -o yaml | A CronJob re-launches attacker containers on a schedule; a DaemonSet runs one pod on every node (fleet-wide miner or backdoor). The spec shows image, command, hostPath mounts and service account. | Compare the creating user and managedFields manager with your deploy pipeline; check kube-system for look-alike names · T1053.007 / T1610 |
| StatefulSet cross-namespace pod (CVE-2026-2270) | K8s audit log: create/update/patch on controllerrevisions (apps) and statefulsets by the same user or service account · pods created by kube-controller-manager in a namespace other than their StatefulSet's | kube-controller-manager restored the whole ControllerRevision object, namespace included, instead of only the spec, so an identity with namespace-scoped write on both StatefulSets and ControllerRevisions could make the controller create a pod in another namespace. | Writes to controllerrevisions by people or workload service accounts are unusual; the StatefulSet and DaemonSet controllers normally write them. The garbage collector deletes the pod unless the attacker set a valid StatefulSet OwnerReference (needs the UID of a StatefulSet in the target namespace). Affected: ≤ v1.34.11, ≤ v1.35.8, ≤ v1.36.4 and v1.37.0; fixed in v1.34.12 / v1.35.9 / v1.36.5 / v1.37.1. Medium (5.9) · T1610 |
| Windows-node kubelet NTLM leak (CVE-2026-76654) | Pod specs with volumeMounts[].subPath · symlinks inside the volume pointing to a UNC path (\\host\share) · Windows node: outbound SMB (445) from the node to an unexpected host | Kubelet on Windows nodes did not reject subPath symlinks that resolve to UNC paths, so a pod could make the kubelet authenticate to an attacker's share and expose the NetNTLMv2 hash of the account the kubelet runs as. | The hash can be cracked, or relayed to act as the node if it is domain-joined. Windows nodes only. Affected kubelet ≤ v1.34.11, ≤ v1.35.8, ≤ v1.36.4 and v1.37.0; fixed in v1.34.12 / v1.35.9 / v1.36.5 / v1.37.1 (UNC symlink targets refused). Medium (5.8) · T1187 |
▸ How to read this: who opened a shell in a pod? Exec, attach and port-forward in the audit log
kubectl exec, attach and port-forward change no object in the cluster: they open a stream to a pod that is already running. The API server records each one in the K8s API audit log as a request on the pods resource with a subresource (exec, attach or portforward). Read it in three steps:
- Match both verbs. Older SPDY clients send an HTTP POST, logged as verb
create. WebSocket clients send an HTTP GET, logged as verbget, and WebSocket is the kubectl default since 1.31. A hunt forcreatealone misses them. - Read who, from where and what.
user.usernameis who ran it,sourceIPsis where from, andrequestURIcarries thecommand=arguments, so it shows what was started in which pod and namespace. - Know where it stops. Commands typed inside an interactive shell are not logged: the event shows the shell being opened, not what was done in it. The audit policy must log the
pods/execsubresource at Metadata or Request level, or there is no event at all.
An exec by a human user or an unexpected service account into a production pod is hands-on-keyboard activity (T1609).
▸ How to read this: who handed out which role? RBAC binding changes in the audit log
In Kubernetes RBAC a role lists permissions, and a binding gives that role to subjects (users, groups or service accounts). Creating or changing a binding is how an identity gains rights, so every create, update or patch on clusterrolebindings or rolebindings (API group rbac.authorization.k8s.io) in the K8s API audit log is worth reading. Read it in three steps:
- Read the binding.
user.usernameis who made the change,requestObject.subjectsis who receives the role andrequestObject.roleRef.nameis which role. A ClusterRoleBinding grants it across the whole cluster; a RoleBinding grants it inside one namespace. - Spot the bindings that hand over the cluster.
cluster-adminbound to a namespace service account, or any role bound tosystem:anonymous/system:unauthenticated. A new cluster-admin binding made by a service account or an unexpected user is privilege escalation and persistence (T1098.006). Bootstrap, upgrade and recovery tooling re-creating bindings is the usual false positive. - Follow the subject. From the binding's timestamp on, hunt what the subject did next: secret reads, privileged pods, DaemonSets. A service account appears in
user.usernameassystem:serviceaccount:<namespace>:<name>.
Cloud & Identity
Cloud evidence is retention-limited and must be exported before it ages out. Pivot on identity (UPN / ARN / principal) and source IP, not on host.
▸ How to read this: following one token across the cloud logs
A cloud sign-in leaves evidence in several separate logs. The sign-in log is the starting point, but it is not the whole story.
- Start in the sign-in log and note the linkable identifiers: User ID, Session ID, Unique Token Identifier, Resource Tenant ID.
- Search the workload audit logs (M365 UAL, resource-side logs) for the same values. This separates what the stolen token did from what the real user did on the same account.
- Corroborate with Graph activity logs (AppId, UserId, IP, RequestUri), if they were enabled before the incident.
Token replay keeps the same session ID across different IPs, so pivot on session/token ID, not just the UPN. If the sign-in log shows nothing, that is not proof of no access: check the other sources.
▸ How to read this: an OAuth consent grant, and what a password reset leaves behind
An illicit consent grant does not need the user's password. The user (or an admin) clicks Accept on an app's permission prompt, and the app (its service principal) then gets tokens of its own for the scopes it asked for.
- Find the grant: search the Entra audit log / UAL for the activity
Consent to application(Entra illicit consent grant). Note who consented, which app, andConsentAction.Permissions.IsAdminConsentorOnBehalfOfAll= True means tenant-wide access. - Judge the scopes:
offline_accessgives the app a refresh token; together withMail.ReadWrite,Files.ReadWrite.AllorChat.Readit fits mailbox and file theft. - Follow what the app did: search Microsoft Graph activity logs by the app's AppId, if they were enabled before the incident.
- Evict it: revoke the grant and the service principal. Resetting the user's password does not remove it.
The same lesson applies to three other rows: a user-managed key from GCP service account key creation, a client secret or certificate found through Entra app / service principal credential added, and a rogue external MFA provider found through Entra Authentication Methods Policy audit all keep working after a password reset. Remove each one on its own. The UAL entry for a consent can take 30 min to 24 h to appear.
| Source | Where / how | What it proves | Caveats · ATT&CK |
|---|---|---|---|
| Entra ID sign-in logs | Entra admin center > Monitoring > Sign-in logs · Graph API · diagnostic export to Log Analytics / storage | Interactive, non-interactive, service-principal and managed-identity sign-ins with IP, app, MFA and Conditional Access results. | Portal retention 7 days (free) / 30 days (P1/P2). Researchers have shown authentication paths that produce no sign-in log entry — absence of logs is not proof of no access. T1078.004 |
| Entra ID audit logs | Entra admin center > Audit logs · Graph auditLogs/directoryAudits | Directory changes: role assignments, app registrations/consents, MFA method changes, CA policy edits. | Watch new credentials on service principals and OAuth consent grants · T1098.001 / T1098.003 |
| M365 Unified Audit Log | Purview audit search · Graph AuditLogQuery API (GA October 2026) · Management Activity API · Search-UnifiedAuditLog (retiring 2027-10-15) | Exchange, SharePoint, OneDrive, Teams, Entra activity — MailItemsAccessed, inbox rules, file downloads, mailbox permission changes. | Confirm auditing is enabled and which license tier (retention) applies; New-InboxRule / Set-Mailbox forwarding = BEC tells · T1114.003 |
| AWS CloudTrail | Event history (90 days, management events) · trails to S3 · CloudTrail Lake (existing customers only since 2026-05-31; AWS points new users to CloudWatch) | API calls: userIdentity.arn, eventName, sourceIPAddress, userAgent, errorCode. | Data events (S3 object, Lambda) and Insights are opt-in. Pivot on userIdentity.arn; check for StopLogging / DeleteTrail · T1685.002 |
| GCP Cloud Audit Logs | Cloud Logging: cloudaudit.googleapis.com/activity (Admin Activity) · /data_access · /system_event | Admin Activity (always on) records config changes; Data Access records reads/writes. | Data Access logs are off by default for most services (BigQuery excepted) — enable before an incident. Verify retention per log bucket. |
| Entra linkable identifiers | Sign-in log fields: User ID · Session ID · Unique Token Identifier · Resource Tenant ID → search workload audit logs for the same values | Ties one stolen token or session to every action it performed in Exchange/SharePoint/Teams after sign-in, separating attacker from user activity on the same account. | Token replay (AiTM) keeps the same session ID across IPs — pivot on session/token ID, not just UPN · T1550.001 / T1550.004 |
| AWS IAM Identity Center events | CloudTrail: userIdentity.type = IdentityCenterUser · user ID + Identity Store ARN (userName / principalId no longer emitted since 2025-07) | Attributes console/CLI activity federated through Identity Center to a specific user. | Old detections keyed on userName/principalId break silently — rekey on user ID / ARN |
| AWS Organizations membership | CloudTrail: AccountJoinedOrganization · AccountDepartedOrganization (new, 2026) | Account added to or removed from the org — attacker moving accounts to dodge SCPs/central logging or onboarding rogue accounts. | Alert alongside LeaveOrganization / InviteAccountToOrganization |
| Microsoft Graph activity logs | Entra diagnostic setting: MicrosoftGraphActivityLogs → Log Analytics / storage / Event Hub (must be enabled) | Every Graph API request by token/app: RequestUri, method, status, AppId, UserId, IPAddress. | Not on by default, so no history before enablement. Key for AzureHound / mailbox / directory enumeration · T1087.004 |
| Entra Authentication Methods Policy audit | Audit logs · activities “Authentication Methods Policy Update” / “Reset” · modifiedProperties | Tenant-level change to which MFA/auth methods are allowed (e.g. enabling a weaker method before an attack). Varonis “TrustSink” (Sept 2026): an attacker who already holds Global Admin or Authentication Policy Admin registers a rogue external MFA provider that shows a fake password page; it survives password resets. | Since Apr–May 2026 only changed properties with old/new values are logged — update parsers/alerts keyed on the old full-policy blob. Remove a rogue external MFA provider before resetting passwords · T1556.006 |
| AWS EKS control-plane logs | CloudWatch log group /aws/eks/<cluster>/cluster (api, audit, authenticator, controllerManager, scheduler) · CloudTrail for the AWS side | Kubernetes API audit trail for EKS; authenticator log maps IAM principals to Kubernetes identities. | Each log type must be enabled per cluster — off by default · correlate with CloudTrail and node IAM roles |
| Entra sign-in log gaps | Sign-in logs vs Graph activity logs vs M365 UAL vs resource-side audit logs | Researchers (e.g. TrustedSec, 2026) showed some Entra authentication paths could succeed without a sign-in log entry (reported fixed by Microsoft). A missing sign-in record is not proof of no access. | Corroborate token use through Graph activity logs, UAL and workload logs — never rely on SigninLogs alone T1078.004 |
| Entra OAuth client-ID spoofing (ROPC probing) | Sign-in logs (incl. non-interactive / failed) · filter on blank or unknown Application name with a well-formed AppId GUID · many random AppIds from one IP | Proofpoint (July 2026, via secondary reports) described password-spray/user-enumeration where requests to the token endpoint use made-up client IDs; a syntactically valid but unregistered ID leaves the application name blank, and token-endpoint responses reveal whether a user exists and whether the password is valid. | Failed-looking entries with blank app name can still mean a valid password was found — hunt per source IP/username, not per app · Reported from secondary sources; confirm against the Proofpoint write-up · T1110.003 T1078.004 |
| AWS CloudTrail network activity events | Trail event selector: NetworkActivity (VPC endpoint events) · off by default | API calls traversing a VPC endpoint, including denied calls — helps spot stolen credentials used from outside the expected network path. | Must be enabled beforehand; not retroactive. |
| AWS VPC Flow Logs | Per VPC, subnet or ENI · to CloudWatch Logs, S3 or Firehose · off by default · default (v2) record: version account-id interface-id srcaddr dstaddr srcport dstport protocol packets bytes start end action log-status | Network 5-tuple per network interface per aggregation window (1 or 10 min) with packets, bytes and ACCEPT / REJECT — lateral movement, C2 and exfiltration volume from cloud hosts. | No payload or DNS names, and traffic to the Amazon DNS resolver, instance metadata (169.254.169.254), Amazon Time Sync, DHCP and ARP is never logged — use Route 53 Resolver query logs for DNS. Not retroactive; records can be skipped under capacity limits. Attacker removal: CloudTrail DeleteFlowLogs · T1685.002 |
| AWS S3 server access logs | Bucket property Server access logging → target bucket (same Region and account; space-delimited text, - for unknown fields) or CloudWatch Logs (JSON) · off by default | One record per request to the bucket: requester (canonical user ID), remote IP, operation (e.g. REST.GET.OBJECT), key, HTTP status, bytes sent, authentication type. Includes anonymous requests and requests that failed authorization (AccessDenied) — bucket enumeration and object download from cloud storage. | Best-effort delivery: records can arrive late or be missing, so absence is not proof. Not retroactive; pair with CloudTrail S3 data events where enabled. Attacker removal: CloudTrail PutBucketLogging · T1530 T1685.002 |
| Entra agent identity sign-ins | Sign-in logs: agentSignIn resource (beta Graph auditLogs/signIns, filter on agent/agentType) · portal filters “Agent type” / “Is Agent” | Sign-ins by AI agent identities (Agent ID user, Agent Identity, Agent Identity Blueprint). Can appear in all four sign-in log types (user-delegated or app-only). | Audit logs record agent activity under the base type: blueprint = application event, agent identity = service principal event. Needs Reports Reader. Based on Microsoft Learn doc text via search mirrors — confirm property names. T1078.004 |
| Graph activity logs for file exfiltration | MicrosoftGraphActivityLogs: RequestUri on drives/items /content · app/token IDs · bulk-request volume per short window | High-fidelity record of OneDrive/SharePoint downloads made through the Graph API, with the app and token that made them. | Only exists if the diagnostic setting was enabled beforehand; correlate with UAL file events and sign-in Session ID · T1530, T1567 |
| SharePoint sync-masqueraded downloads | M365 UAL: FileSyncDownloadedFull · UserAgent · ClientIP · device ID | Technique published by Varonis in April 2024 (reported to Microsoft November 2023) where user-agent manipulation makes browser/Graph downloads appear as sync-client events instead of FileDownloaded. | A burst of FileSyncDownloadedFull from a new device or odd user-agent is bulk exfil until proven otherwise · hunt on volume per window, not only FileDownloaded · T1213.002 |
| Unified Audit Log via Graph (AuditLogQuery API) | Graph security/auditLog/queries · create query → poll → fetch records | Programmatic UAL search, generally available from October 2026 and the replacement for Search-UnifiedAuditLog; exports up to 1 million records per query. | Move scripts off Search-UnifiedAuditLog before 2027-10-15; scripts written during the preview may still call the /beta path. |
| CloudTrail log file integrity | S3 digest files (CloudTrail-Digest/) · aws cloudtrail validate-logs · trail setting Enable log file validation | SHA-256 hash chain proves delivered log files were not modified or deleted after delivery — use before relying on logs as evidence. | Validation only works if enabled before the incident; attackers target StopLogging / DeleteTrail / bucket policy · T1685.002 |
| AWS credential-use triage events | CloudTrail: GetCallerIdentity · ConsoleLogin (responseElements, additionalEventData.MFAUsed) · AssumeRole (userIdentity.type = AssumedRole, sessionContext) | GetCallerIdentity is a common first call with newly stolen keys; ConsoleLogin shows interactive sign-ins and whether MFA was used; AssumeRole chains show how a principal pivoted between roles and accounts. | GetCallerIdentity needs no permissions and cannot be denied, so it reveals the key owner even from a locked-down credential · pivot on sourceIPAddress, userAgent and accessKeyId · T1078.004 T1550.001 |
| Defender XDR sign-in tables | Advanced hunting: EntraIdSignInEvents (replaces AADSignInEventsBeta on 2026-10-19) · EntraIdSpnSignInEvents (service principals) | Entra interactive and non-interactive sign-ins in Defender XDR hunting, joinable to device and email tables; SessionId and CorrelationId pivot to other logs. | Queries on the beta table migrate automatically on 2026-10-19 — update saved hunts and scripts that name the old table. Needs Entra ID P2; Defender XDR retention (30 days) is shorter than a Log Analytics export · T1078.004 |
| GCP service account key creation | Admin Activity audit log: protoPayload.methodName="google.iam.admin.v1.CreateServiceAccountKey" · gcloud iam service-accounts keys list --iam-account=<sa> | Who minted a long-lived user-managed key for a service account (principal in authenticationInfo). Later API calls made with that key carry authenticationInfo.serviceAccountKeyName, tying actions back to the key. | Key works from anywhere until deleted or disabled — resetting the creator's credentials does not revoke it · org policy iam.disableServiceAccountKeyCreation blocks it · T1098.001 |
| Entra illicit consent grant | Entra audit log / UAL: activity Consent to application · fields ConsentContext.IsAdminConsent, ConsentContext.OnBehalfOfAll, ConsentAction.Permissions, ConsentAction.Reason | Which user consented, to which app (service principal), and which scopes. IsAdminConsent = True or OnBehalfOfAll = True means tenant-wide access; offline_access with Mail.ReadWrite, Files.ReadWrite.All or Chat.Read fits mailbox and file theft; Reason "Risky application detected" = Microsoft flagged the app at consent time. | The app keeps its refresh token after the user's password reset — revoke the grant and the service principal, not just the user's sessions · UAL entry can take 30 min to 24 h to appear · T1528 |
| Entra device code phishing | Sign-in logs: Authentication protocol = Device code flow · Original transfer method = Device code flow (later sign-ins in the same session) · Defender XDR: ErrorCode 50199 then 0 within minutes · audit log: new device registrations by “Device Registration Service” | A user entered an attacker-supplied code at microsoft.com/devicelogin, so the attacker's session received tokens for that user (Storm-2372, Microsoft, February 2025). The session stays protocol-tracked: later sign-ins in it keep Original transfer method = Device code flow even over another protocol. Microsoft saw the actor then switch to the Microsoft Authentication Broker client to get a refresh token and register a device, so check device registrations close to the sign-in. | The successful device code sign-in comes from the attacker's IP with the user's real MFA — revoke refresh tokens (revokeSignInSessions) and remove devices registered after it · inventory legitimate use with the Authentication protocol filter, then block the flow with the Conditional Access “Authentication flows” condition · T1528 T1566.002 |
| Entra app / service principal credential added | Entra audit log / UAL (AzureActiveDirectory workload): activities Update application – Certificates and secrets management and Add service principal credentials · modifiedProperties KeyDescription (key ID, type, usage) | Who added a client secret or certificate to which app registration or service principal, and when. The new credential lets the attacker sign in as the app (client-credentials flow) with whatever Graph/API permissions it already has — check that app's service principal sign-ins for the new key ID right after the change. | Survives user password resets and MFA changes — remove the added key and review the app's permissions · legitimate secret rotation also triggers it, so baseline expected initiators · T1098.001 |
| AWS IAM credential persistence | CloudTrail (eventSource iam.amazonaws.com, recorded in us-east-1): CreateAccessKey · CreateLoginProfile / UpdateLoginProfile · EC2 CreateKeyPair / ImportKeyPair · aws iam list-access-keys --user-name <user> | Who added a new access key or console password to an IAM user (requestParameters.userName vs the caller in userIdentity.arn; a different user = adding credentials to someone else). A console password on a user that was programmatic-only, or a second active key, is a persistence tell. Later activity with the key carries its userIdentity.accessKeyId. | IAM events are global and land in us-east-1: a single-Region trail elsewhere misses them · new keys survive a password reset — deactivate and delete them · T1098.001 |
| Azure Activity Log | Azure portal > Monitor > Activity log · Log Analytics table AzureActivity (via a subscription diagnostic setting) · az monitor activity-log list | Subscription control plane (Azure Resource Manager): who created, changed, deleted or ran an action on a resource. caller (UPN or SPN), operationName, category Administrative, caller IP and correlationId (groups the events of one operation); identity details under claims. | kept 90 days in Azure, then deleted — export to a workspace or storage before it ages out · records management-plane calls only, not data reads inside resources or the VM · separate from Entra sign-in / audit logs |
| Azure VM Run Command | Activity Log: MICROSOFT.COMPUTE/VIRTUALMACHINES/RUNCOMMAND/ACTION · Windows VM: C:\Packages\Plugins\Microsoft.CPlat.Core.RunCommandWindows\<ver>\ (Downloads\script*.ps1, Status) · C:\WindowsAzure\Logs\Plugins\Microsoft.CPlat.Core.RunCommandWindows\<ver>\RunCommandExtension.log · Linux VM: /var/lib/waagent/run-command/download/<n>/ (script.sh, stdout, stderr) · /var/log/azure/run-command/handler.log | A script pushed into a VM through the VM agent from the Azure control plane — no RDP/SSH logon on the host. The Activity Log names the caller; the plugin folder on the VM holds the script and its result. On Windows the script runs as SYSTEM (powershell.exe … -File script*.ps1). | the Activity Log does not hold the script text — collect the plugin folder from the VM early · version folder names vary · APT29 used it · T1651 |
| GCP IAM policy change (role grant) | Admin Activity audit log: protoPayload.methodName="SetIamPolicy" (project / folder / org) · google.iam.admin.v1.SetIAMPolicy (on a service account) · delta in protoPayload.serviceData.policyDelta.bindingDeltas (action ADD / REMOVE, role, member) | Who granted which role to which principal and when (caller in authenticationInfo.principalEmail). Filter on bindingDeltas.action="ADD" to list every new grant during the incident window; a REMOVE soon after an ADD can be an attacker cleaning up. | look for owner / editor, roles/iam.serviceAccountTokenCreator or roles/iam.serviceAccountUser, external accounts, allUsers / allAuthenticatedUsers · Admin Activity is always on · T1098.003 |
| GCP service account impersonation | Data Access audit log, iamcredentials.googleapis.com: protoPayload.methodName="GenerateAccessToken" · GenerateIdToken · SignBlob · SignJwt · in later calls: protoPayload.authenticationInfo.serviceAccountDelegationInfo[].firstPartyPrincipal.principalEmail | A principal minting short-lived tokens or signatures as a service account. Calls made with the token show the service account as the principal; the human or workload behind it is in serviceAccountDelegationInfo, so pivot there instead of stopping at the service account. | not logged unless Data Access logs are enabled for the IAM API (this also turns them on for the Service Account Credentials API) — enable before an incident · not every service records delegation info · T1548.005 |
| GCP log sink & log bucket tampering | Admin Activity audit log, logging.googleapis.com: protoPayload.methodName="google.logging.v2.ConfigServiceV2.DeleteSink" · UpdateSink · DeleteBucket · gcloud logging sinks list | Someone deleting or rewriting a log export sink (logs stop reaching the SIEM, BigQuery or Storage export) or deleting a log bucket. Exclusion filters on the _Default sink override its inclusion filters, so an added or edited exclusion silently drops logs. The _Required sink and bucket (Admin Activity, System Event) cannot be changed, excluded or deleted, and keep 400 days. | sink changes are not retroactive: logs routed while the sink was gone or filtered are not exported later — read _Required for the gap · a deleted bucket stays pending for 7 days and can be restored · infrastructure-as-code service accounts are the usual false positive · T1685.002 |
| AWS Secrets Manager secret access | CloudTrail (eventSource secretsmanager.amazonaws.com): GetSecretValue (one entry per secret, also for each secret in a BatchGetSecretValue call) · ListSecrets · DescribeSecret · PutResourcePolicy · DeleteSecret | Which identity read which secret (requestParameters.secretId, userIdentity.arn, sourceIPAddress, userAgent). A ListSecrets sweep followed by many GetSecretValue calls from one identity, a first-time reader, or a CLI user agent on a role that normally runs in EC2 or Lambda points to credential harvesting. | the log never shows the secret value: treat every secret read in the window as exposed and rotate it · baseline which roles read which secrets · T1555.006 |
| AWS EBS snapshot / AMI sharing | CloudTrail ec2.amazonaws.com: ModifySnapshotAttribute with createVolumePermission add (user ID = recipient account; group all = public) · ModifyImageAttribute launchPermission for AMIs · owner-side SharedSnapshotCopyInitiated / SharedSnapshotVolumeCreated | A full disk copy handed to another AWS account or made public. The recipient can copy it or create a volume and read the whole disk in their own account, outside your logs. Pivot on userIdentity.arn, source IP and the snapshot ID back to a preceding CreateSnapshot / CopySnapshot. | An add for an account ID outside your organization, or group all, is data exfiltration of a whole volume. Encrypted snapshots cannot be made public, and ones encrypted with the default KMS key cannot be shared with other accounts, so check the snapshot's key and any CopySnapshot that produced it. Same-organization sharing is the usual false positive · T1537 |
| AWS IAM policy & role trust changes | CloudTrail (eventSource iam.amazonaws.com, recorded in us-east-1): AttachUserPolicy / AttachRolePolicy / AttachGroupPolicy (requestParameters.policyArn) · PutUserPolicy / PutRolePolicy (inline) · CreatePolicyVersion (setAsDefault) · UpdateAssumeRolePolicy (requestParameters.policyDocument) | Who gave an identity more permissions, and who changed which principals may assume a role. A new trust policy that names an outside account ID or a broad principal lets that account call AssumeRole into the role from outside; later use shows as AssumeRole events against the role ARN. | AdministratorAccess (or *:* inline) attached by a non-admin pipeline identity, or an external account added to a role trust policy, is privilege escalation and persistence · compare the old and new trust policy and restore it · IaC tooling (Terraform, CloudFormation user agents) is the usual false positive · T1098.003 |
| Entra Conditional Access policy changes | Entra audit log: Service Conditional Access · Category Policy · activities Add conditional access policy / Update conditional access policy / Delete conditional access policy · Log Analytics AuditLogs (OperationName) · old / new JSON in TargetResources > modifiedProperties | Who created, changed or deleted which Conditional Access policy, with the policy JSON before and after the change. Retained 30 days by default unless exported through a diagnostic setting. | A new trusted IP range or named location, a removed MFA grant, a new user or group exclusion, or a policy switched to report-only or off is persistence and defense evasion · diff old and new values and correlate with the actor's sign-ins · T1556.009 |
| Entra domain federation changes | Entra audit log (Category DirectoryManagement) / UAL: activities Set domain authentication · Set federation settings on domain · precursors Add unverified domain / Verify domain · Log Analytics AuditLogs (OperationName) · current settings: Get-MgDomainFederationConfiguration | Who converted a domain to federated or changed a federated domain's trust settings, and which domain. A federation the attacker controls (their own IdP or an added signing certificate) issues SAML tokens the tenant accepts for any user in that domain — the trust change APT29 made in the SolarWinds campaign. | The audit event names the domain but not the new configuration — pull it from Graph and compare issuer URI and signing certificates with the real IdP. Federation changes are rare: an unexpected one, a new secondary signing certificate, or a newly verified domain set to federated is a tenant-wide backdoor that does not depend on any user's password · T1484.002 |
| AWS S3 bucket policy, ACL & Block Public Access changes | CloudTrail management events (eventSource s3.amazonaws.com, 90 days in event history): PutBucketPolicy · PutBucketAcl · PutBucketPublicAccessBlock (bucket) · PutAccountPublicAccessBlock (account) · bucket in requestParameters.bucketName | Who opened a bucket and to whom: the policy JSON or ACL grant in requestParameters names the principals; a Block Public Access call that sets BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy or RestrictPublicBuckets to false removes the guard rail first. Pivot on userIdentity.arn and source IP, then to S3 data events or server access logs for the reads that followed. | A policy Allow for an account ID outside your organization, a "Principal": "*" grant, an AllUsers / AuthenticatedUsers ACL grant, or Block Public Access weakened shortly before a policy change = bucket backdoored for exfiltration. These are configuration events only; object reads are data events and are absent unless a trail already collected them. Partner and vendor cross-account access is the usual false positive · T1537 · T1530 · T1686.001 (Block Public Access weakened, per SigmaHQ) |
| Exchange Online mailbox delegation | UAL record type ExchangeAdmin: Add-MailboxPermission (AccessRights FullAccess) · Add-RecipientPermission (SendAs) · Set-Mailbox with GrantSendOnBehalfTo · Add-MailboxFolderPermission / Set-MailboxFolderPermission · Search-UnifiedAuditLog -RecordType ExchangeAdmin -Operations Add-MailboxPermission,Add-RecipientPermission,Set-Mailbox | Who gave another account access to a mailbox: the target mailbox in Parameters.Identity, the grantee in User / Trustee, the right in AccessRights, the caller in UserId. FullAccess reads the whole mailbox but does not include SendAs or Send on Behalf, which are separate grants. A FullAccess mailbox is auto-mapped into the grantee's Outlook unless -AutoMapping $false was passed (a quiet grant). Folder permissions given to the Default or Anonymous user on the mailbox root or Inbox let other accounts keep reading those folders. | Delegation is tied to the grantee, so it survives the victim's password reset and session revocation — list grants with Get-MailboxPermission / Get-RecipientPermission and remove them · T1098.002 |
▸ How to read this: Azure VM Run Command, from the control plane to files on the VM
Run Command pushes a script into a VM through the VM agent, from the Azure control plane. There is no RDP or SSH logon on the host, so a hunt that starts from logon events misses it. The evidence is split between Azure and the VM:
- Find the call in the control plane: in the Azure Activity Log, look for
MICROSOFT.COMPUTE/VIRTUALMACHINES/RUNCOMMAND/ACTION. Note thecaller(UPN or SPN), caller IP, time andcorrelationId. - Collect the result from the VM: the Azure VM Run Command plugin folder holds the downloaded script and its status or output, and the extension log records the run. On Windows the script runs as SYSTEM.
- Pivot on the caller: the Activity Log is separate from the Entra logs, so look up how that UPN or SPN signed in through the Entra ID sign-in logs.
Two gaps decide the order of work: the Activity Log does not hold the script text, and it is kept only 90 days in Azure. Export the Activity Log and collect the plugin folder from the VM early.
▸ How to read this: GCP service account impersonation, from the role grant to the real caller
In GCP an attacker often acts as a service account, not as themselves, and the calls that follow name the service account. A typical path has three steps, and each one is logged in a different place:
- Grant: a role that lets a principal act as a service account (for example
roles/iam.serviceAccountTokenCreator) is added. Find it through GCP IAM policy change (role grant): the Admin Activity log is always on, andbindingDeltas.action="ADD"lists every new grant. - Impersonate: the principal mints a short-lived token or signature as the service account. GCP service account impersonation covers these
iamcredentials.googleapis.comcalls, which are in the Data Access log only if it was enabled for the IAM API. - Act: calls made with the token show the service account as the principal. Read
serviceAccountDelegationInfofor the human or workload behind it, and pivot from there.
If Data Access logging was off, step 2 left no record: work from the grant in step 1 and the delegation info in step 3, where the service records it. A GCP service account key creation is the long-lived alternative to step 2: the key keeps working after a credential reset until it is deleted or disabled.
▸ How to read this: AWS VPC Flow Logs, what a record shows and what it never shows
AWS VPC Flow Logs record metadata about traffic, not the traffic itself. Each record answers four questions for one network interface (ENI) in one aggregation window of 1 or 10 minutes:
- Which interface:
account-idandinterface-idtie the record to an ENI, and so to the instance behind it. - Who talked to whom: the 5-tuple
srcaddr dstaddr srcport dstport protocol. Traffic to other hosts in the VPC shows lateral movement, traffic to outside addresses shows C2. - How much and when:
packetsandbytesbetweenstartandend, which is how exfiltration volume shows up. - Allowed or not:
action(ACCEPT / REJECT) andlog-status.
Some traffic is never logged, even with flow logs on: the Amazon DNS resolver, instance metadata (169.254.169.254), Amazon Time Sync, DHCP and ARP. Records hold no DNS names either, so for DNS use Route 53 Resolver query logs. Flow logs are off by default, not retroactive, and records can be skipped under capacity limits, so a missing record is not proof of no traffic. Removal of a flow log shows in AWS CloudTrail as DeleteFlowLogs.
▸ How to read this: who read the bucket? Where an S3 request is recorded
One request to an S3 bucket can leave a record in up to three places, and which ones exist depends on what was switched on before the incident:
- AWS CloudTrail management events: bucket configuration calls such as
PutBucketLogging, kept 90 days in event history. Reading an object is not a management event, so it is not here. - CloudTrail S3 data events: object-level calls with the usual CloudTrail fields (
userIdentity.arn,sourceIPAddress). Opt-in, so they exist only if a trail was already collecting them. - AWS S3 server access logs: one record per request to the bucket, including anonymous requests and requests that failed authorization (AccessDenied). Off by default.
A server access log record answers who (requester canonical user ID, authentication type), from where (remote IP), did what (operation such as REST.GET.OBJECT, and the key) and with what result (HTTP status, bytes sent). Runs of AccessDenied or anonymous requests point to bucket enumeration; successful GETs with bytes sent point to object download. Delivery is best-effort, so a missing record is not proof that no request was made. If logging stopped mid-incident, look for PutBucketLogging in CloudTrail.
▸ How to read this: a whole disk handed to another account (EBS snapshot and AMI sharing)
An EBS snapshot is a full copy of a volume. Sharing it is a permission change: the recipient account then reads the disk in its own account, outside your logs. What you can see is in AWS CloudTrail (ec2.amazonaws.com), in three steps:
- Make the copy:
CreateSnapshotorCopySnapshot. A copy can carry a different KMS key, so note which key the shared snapshot uses and which call produced it. - Share it:
ModifySnapshotAttributewith acreateVolumePermissionadd. A user ID is the recipient account; groupallmakes the snapshot public. For an AMI the call isModifyImageAttributewithlaunchPermission. - Use it: the recipient copies the snapshot or creates a volume from it. That runs in their account, but your trail gets the owner-side
SharedSnapshotCopyInitiated/SharedSnapshotVolumeCreatedevents.
The snapshot's key decides what can leave: an unencrypted snapshot can be shared with any account or made public, an encrypted one can never be made public, and one encrypted with the default KMS key cannot be shared with other accounts at all. An add for an account outside your organization, or for group all, is exfiltration of a whole volume; sharing inside the organization is the usual false positive. The row is AWS EBS snapshot / AMI sharing.
▸ How to read this: two ways to widen an AWS identity's reach (permissions and role trust)
In AWS IAM, what an identity can do and who may become a role are set by two different policies, and an attacker can change either one. Every change is an AWS CloudTrail event from iam.amazonaws.com, recorded in us-east-1 whatever Region the workload runs in.
- The permissions policy (what it can do). Three ways to add rights: attach a managed policy (
AttachUserPolicy/AttachRolePolicy/AttachGroupPolicy;requestParameters.policyArnnames it), write an inline policy (PutUserPolicy/PutRolePolicy), or add a new version of a managed policy withCreatePolicyVersionandsetAsDefault, which changes it for every identity it is attached to. - The trust policy (who may become this role).
UpdateAssumeRolePolicyreplaces it, andrequestParameters.policyDocumentholds the new version. An outside account ID or a broad principal there lets that account callAssumeRoleinto the role from outside; later use shows asAssumeRoleevents against the role ARN.
AdministratorAccess (or *:* inline) attached by a non-admin pipeline identity, or an external account added to a trust policy, is privilege escalation and persistence (T1098.003). IaC tooling (Terraform, CloudFormation user agents) is the usual false positive. Compare the old and new trust policy and restore it. New access keys and console passwords are the other way to keep access: see AWS IAM credential persistence.
▸ How to read this: what a Conditional Access change can quietly switch off
Conditional Access policies decide which sign-ins must meet which controls, such as MFA or a trusted location. An attacker with enough rights can weaken them instead of fighting them, and every add, update or delete is recorded in the Entra ID audit logs (Service Conditional Access, Category Policy). Read it in three steps:
- Find the change before it ages out. The activities are
Add conditional access policy,Update conditional access policyandDelete conditional access policy; in Log Analytics they are inAuditLogsunderOperationName. The audit log is kept 30 days by default unless it is exported through a diagnostic setting. - Diff old and new.
TargetResources>modifiedPropertiesholds the policy JSON before and after the change. A new trusted IP range or named location, a removed MFA grant, a new user or group exclusion, or a policy switched to report-only or off is persistence and defense evasion (T1556.009). - Correlate with the actor's sign-ins. Check the Entra ID sign-in logs of the account that made the change, and the sign-ins the weakened policy now lets through.
▸ How to read this: one federation change, and every account in the domain signs in
A federated domain hands sign-in to an outside identity provider (IdP): the tenant accepts any SAML token signed by the certificate it trusts for that domain. Whoever controls that trust can sign in as any user in the domain without knowing a password, which is the trust change APT29 made in the SolarWinds campaign (T1484.002). Read it in three steps:
- Find the change. In the Entra ID audit logs (Category
DirectoryManagement, also in the UAL and in Log AnalyticsAuditLogsunderOperationName),Set domain authenticationconverts a domain to federated andSet federation settings on domainchanges the trust of a domain that is already federated. For a new domain,Add unverified domainandVerify domaincome first. - Pull the configuration the event leaves out. The event names the domain but not the new settings. Run
Get-MgDomainFederationConfigurationand compare the issuer URI and the signing certificates with your real IdP. - Treat any surprise as tenant-wide. Federation changes are rare: an unexpected one, a new secondary signing certificate, or a newly verified domain set to federated is a backdoor for every account in that domain, and resetting a user's password does not close it.
▸ How to read this: who opened the bucket, and to whom
Opening an S3 bucket to an outsider takes a configuration change, and configuration changes are CloudTrail management events, so they are on record even when nobody was logging object reads. The "Who read the bucket?" diagram earlier in this section follows the reads; this one answers who opened the bucket. Read it in three steps:
- Find the changes. In AWS CloudTrail (eventSource
s3.amazonaws.com, 90 days in event history) look forPutBucketPolicyandPutBucketAcl, and for the Block Public Access calls that often come first:PutBucketPublicAccessBlockfor one bucket,PutAccountPublicAccessBlockfor the whole account. The bucket is inrequestParameters.bucketName. - Read the grant. The policy JSON or the ACL grant in
requestParametersnames who was let in. A Block Public Access call that setsBlockPublicAcls,IgnorePublicAcls,BlockPublicPolicyorRestrictPublicBucketsto false removes a guard rail. An Allow for an account outside your organization,"Principal": "*", an AllUsers / AuthenticatedUsers ACL grant, or Block Public Access weakened shortly before a policy change is a bucket backdoored for exfiltration. Partner and vendor cross-account access is the usual false positive. - Follow the actor, then the reads. Pivot on
userIdentity.arnand source IP. The reads that followed are data events, absent unless a trail was already collecting them; otherwise check AWS S3 server access logs, if logging was on.
▸ How to read this: what a GCP sink change cuts off, and where the record survives
In Google Cloud every log entry passes through the Log Router, which sends it on through sinks. Two are built in: _Required, which holds the Admin Activity and System Event audit logs, cannot be changed, excluded or deleted, and keeps 400 days; and _Default. The export sinks that feed a SIEM, BigQuery or Storage are the ones an attacker turns off. Read the figure in three steps:
- Find the change. In the Admin Activity audit log (GCP Cloud Audit Logs,
logging.googleapis.com) look forprotoPayload.methodNamegoogle.logging.v2.ConfigServiceV2.DeleteSink,UpdateSinkorDeleteBucket. Admin Activity entries go to_Required, so the tampering call itself stays on record. - Work out what was lost. A deleted or rewritten export sink stops logs reaching the SIEM, BigQuery or Storage export. Exclusion filters on the
_Defaultsink override its inclusion filters, so an added or edited exclusion silently drops logs. A deleted log bucket stays pending for 7 days and can be restored. - Close the gap. Sink changes are not retroactive: logs routed while the sink was gone or filtered are never exported later, so read
_Requiredfor that window. List the sinks as they are now withgcloud logging sinks list. Infrastructure-as-code service accounts are the usual false positive.
▸ How to read this: who else can read or send as a mailbox, and why a reset does not stop them
Mailbox delegation gives a second account rights over someone else's mailbox. It is a quiet way to keep reading mail after the victim's account is cleaned up. Read the figure in three steps:
- Four separate grants. FullAccess (
Add-MailboxPermission) reads the whole mailbox but does not include Send As (Add-RecipientPermission) or Send on Behalf (Set-Mailbox -GrantSendOnBehalfTo). Folder permissions (Add-MailboxFolderPermission/Set-MailboxFolderPermission) given toDefaultorAnonymouson the root or Inbox let other accounts keep reading those folders. Check all four. - Read the ExchangeAdmin record. In the Unified Audit Log the target mailbox is in
Parameters.Identity, the grantee inUser/Trustee, the right inAccessRightsand the caller inUserId. A FullAccess grant made with-AutoMapping $falsedoes not appear in the grantee's Outlook: a quiet grant. - Remove the grant, not just the password. Delegation is tied to the grantee, so a password reset and session revocation on the victim leave it in place. List grants with
Get-MailboxPermission/Get-RecipientPermissionand remove them. The M365 Unified Audit Log row covers what else lands in the same log.