DFIR Field Reference // v1.33 — 2026-11-07

Windows event IDs · forensic artifacts · persistence · memory · Linux triage — fully offline, single file

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

IDLogMeaning / investigative valueNotes · ATT&CK
4624SecuritySuccessful logon. Key fields: Logon Type, Account, Source IP/Workstation, Logon ID (correlate to 4634/4672).See Logon Types table. T1078
4625SecurityFailed 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 / 4647SecurityLogoff / user-initiated logoff. Session duration via matching Logon ID with 4624.Type 3 sessions log off almost instantly — normal.
4648SecurityLogon with explicit credentials (runas, /netonly, some pass-the-hash tooling).Watch for user→admin cred switches. T1078, T1550.002
4672SecuritySpecial privileges assigned at logon — fires for admin-equivalent logons.Pair with 4624 same Logon ID.
4776Security (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 / 4025Microsoft-Windows-NTLM/OperationalNTLMv1-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
4768Security (DC)Kerberos TGT requested — account authenticated to domain.Result 0x6 = bad username; 0x12 = disabled/locked.
4769Security (DC)Kerberos service ticket (TGS) requested for an SPN.Kerberoasting: many 4769s, RC4 (0x17) tickets T1558.003
4771Security (DC)Kerberos pre-auth failed. 0x18 = bad password.Kerberos brute-force indicator. T1110
4740Security (DC)Account locked out — includes caller computer name (source of failures).Fast pivot to the attacking host.
4778 / 4779SecuritySession reconnected / disconnected (RDP + fast user switching). Includes client name and IP.RDP session tracking even without full logon. T1021.001

Account & Group Manipulation

IDLogMeaning / investigative valueNotes · ATT&CK
4720SecurityUser account created. Subject = who created it.Rogue local admin accounts. T1136
4722 / 4725SecurityAccount enabled / disabled.
4723 / 4724SecurityPassword change attempt (by user) / password reset (by admin or attacker).4724 on privileged account = takeover pattern. T1098
4726SecurityAccount deleted — cleanup of attacker-created accounts, or removal of legitimate users' access.T1070.009 (cleanup) · T1531 (access removal)
4728 / 4732 / 4756SecurityMember added to global / local / universal security group. Watch Domain Admins, Administrators, Remote Desktop Users.T1098
4738SecurityUser account changed (UAC flags, expiry, etc.).
4765 / 4766Security (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
4781SecurityAccount renamed — e.g. rename + recreate of built-in accounts.
4794Security (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 / 4742Security (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

IDLogMeaning / investigative valueNotes · ATT&CK
4688SecurityNew 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
4689SecurityProcess exited — bound the execution window.
SysmonSysmon/OperationalProcess 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.
4104PowerShell/OperationalScript block logging — actual (de-obfuscated) code executed. The single best PowerShell artifact.Level=Warning auto-logs "suspicious" blocks even if disabled. T1059.001
4103PowerShell/OperationalModule logging — pipeline execution detail.
400 / 403Windows PowerShellEngine start / stop — PowerShell ran even on legacy systems. HostApplication field shows the command line.Works back to PSv2.
8003 / 8004AppLocker/EXE and DLLEXE/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 / 3077CodeIntegrity/OperationalApp 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.

  1. 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.
  2. 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.

Application control events: AppLocker vs App Control for Business, audit vs enforced A grid with one line per feature and kind of file, and columns for the log, audit mode and enforced mode. AppLocker EXE and DLL rules log to AppLocker/EXE and DLL: 8003 in audit mode (allowed, would have been blocked, the file ran) and 8004 when blocked. AppLocker MSI and script rules log to AppLocker/MSI and Script: 8006 in audit mode and 8007 when blocked. App Control for Business files log to CodeIntegrity/Operational: 3076 in audit mode (the file loaded) and 3077 when blocked, with one 3089 per signature joined on the Correlation ActivityID. Scripts and MSI files checked by a script host log to AppLocker/MSI and Script: 8028 in audit mode and 8029 when blocked, where the script can still run in restricted mode, with signatures in 8038. Events exist only where a policy is deployed. what was checked log (where to look) audit mode: nothing stopped enforced: blocked AppLocker EXE / DLLAppLocker rule AppLocker/EXE and DLL 8003would have been blockedallowed: the file ran 8004blocked MSI / scriptAppLocker rule AppLocker/MSI and Script 8006would have been blockedallowed: it ran 8007blocked App Control for Business (WDAC) file (binary)App Control policy CodeIntegrity/Operational3089one per signaturesame Correlation ActivityID 3076would have been blockedthe file loaded 3077blocked script / MSIchecked by a script host AppLocker/MSI and Script8038signature events 8028would have been blocked 8029blockedcan still run restricted Audit mode stops nothing: 3076 / 8003 / 8006 are a list of unapproved code that ran. No policy deployed, no events: an empty log does not show that nothing ran. Join a 3076 / 3077 to its 3089 signature events on the Correlation ActivityID (System data). Conceptual map built from the AppLocker (8003 / 8004) and App Control (3076 / 3077) rows above.
Fig. — Which feature and which mode wrote the event, and whether the file ran. The audit-mode column is the one that lists code that was not stopped.

Services & Scheduled Tasks

IDLogMeaning / investigative valueNotes · ATT&CK
7045SystemNew service installed — service name, binary path, account. PsExec creates PSEXESVC; many C2s install odd one-word services.T1543.003, T1569.002
4697SecurityService installed (Security-side twin of 7045).Requires audit subcategory.
7034 / 7035 / 7036 / 7040SystemService crashed / start-stop control / state change / start-type changed.7040 to "disabled" on AV/EDR services = tampering. T1685
4698 / 4699SecurityScheduled task created / deleted — full task XML embedded in the event.T1053.005
4700 / 4701 / 4702SecurityTask enabled / disabled / updated.
106 / 140 / 141TaskScheduler/OperationalTask registered / updated / deleted.Disabled by default on newer Win10+.
200 / 201TaskScheduler/OperationalTask action started / completed — proves the task actually ran.

RDP & Remote Access

IDLogMeaning / investigative valueNotes · ATT&CK
1149TS-RemoteConnectionManager/OperationalRDP network connection reached the host — user + source IP. Fires before auth completes.Connection attempt, not proof of logon. T1021.001
21 / 22TS-LocalSessionManager/OperationalSession logon succeeded / shell start — user, session, source IP.The definitive interactive RDP record.
23 / 24 / 25TS-LocalSessionManager/OperationalLogoff / disconnect / reconnect — build the full session timeline.
4624 type 10SecurityRemoteInteractive logon = RDP.Type 7 reconnects to existing session.
98 / 131RdpCoreTS/OperationalTCP 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.

  1. 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.
  2. Authenticate: 4624 type 10 in Security is the RDP logon; failures land in 4625.
  3. Session: 21 / 22 (LocalSessionManager) confirm the session logon and shell start.
  4. 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.

One RDP session, event by event, on the client and on the RDP host The client host keeps 4648 when explicit credentials are used, Sysmon 3 for the client process connection, the RDP client MRU and the RDP bitmap cache. On the destination host the session runs through five stages, one log per lane. Connect: RdpCoreTS 98 / 131 and TS-RemoteConnectionManager 1149, which fires before authentication. Authenticate: Security 4624 type 10, or 4625 on failure. Session: TS-LocalSessionManager 21 / 22. Disconnect and reconnect: Security 4778 / 4779 and TS-LocalSessionManager 24 / 25. Logoff: Security 4634 / 4647 and TS-LocalSessionManager 23. Client IP and account link the two hosts; Logon ID links the Security events. CLIENT (source)where RDP started 4648explicit creds Sysmon 3client process → host RDP client MRUhosts + user hint RDP bitmap cachescreen tiles RDP · link on client IP + account RDP HOSTone lane per log 1Connect 2Authenticate 3Session 4Disconnect 5Logoff RdpCoreTS/Operational TS-RemoteConnectionManager/Operational SecuritySecurity.evtx TS-LocalSessionManager/Operational 98 / 131 131 TCP accepted 98 connected 1149 user + source IP before auth 4624 type 10 RDP logon 4625 = failed 4778 / 4779 reconnect / disconnect + IP 4634 / 4647 logoff same Logon ID 21 / 22 logon · shell user + source IP 23 / 24 / 25 24 disconnect · 25 reconnect 23 logoff 1149 alone = a connection attempt, not proof of logon. 98 / 131 can survive selective log clearing. Join lanes on user + source IP; Security events on Logon ID. Conceptual order built from the RDP rows below; not every session writes every event.
Fig. — An RDP session written across four logs on the host, plus the client-side trail.
▸ 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.

  1. 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).
  2. 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: evidence on the source host versus the destination host The source host records explicit credential use 4648, the process network connection Sysmon 3 and the RDP client MRU. The destination host records the network logon 4624 type 3 or type 10, share access 5140 and 5145, RDP events 1149, 21, 22, 98 and 131, WinRM events 91 and 168, and service install 7045 or 4697. SOURCE HOST (A) DESTINATION HOST (B) Who / what launched it 4648explicit credentials used Sysmon 3process opened the connection RDP MRUNTUSER.DAT RDP targets outbound view: user, process, target What arrived 4624type 3 network · type 10 RDP 5140 / 5145share + file path 7045 / 4697service installed (PsExec) 1149 · 21/22 · 98/131RDP connection, session, core TS 91 / 168WinRM session + user inbound view: logon, share, method trail SMB · RDP · WinRM · WMI source IP + account link the two hosts Conceptual map: which log lives where. Not every method writes every event; see the tables below.
Fig. — Lateral movement: the destination proves arrival, the source explains the origin.

Lateral Movement, Shares & WMI

IDLogMeaning / investigative valueNotes · ATT&CK
5140SecurityNetwork share accessed — share name + source IP.ADMIN$ / C$ / IPC$ access = admin lateral movement T1021.002
5145SecurityDetailed share access — relative file path requested on the share. PsExec writes PSEXESVC.exe to ADMIN$.Very verbose; often on DCs only.
4624 type 3SecurityNetwork logon — SMB, WMI, WinRM, mapped drives on the destination host.The destination-side fingerprint of most lateral movement.
5857WMI-Activity/OperationalWMI provider loaded.
5859 / 5860 / 5861WMI-Activity/OperationalWMI event subscription activity — 5861 = permanent subscription (filter + consumer) = persistence.T1546.003
91 / 168WinRM/OperationalWinRM session created / authenticating user — PowerShell Remoting trail.T1021.006
59 / 60Bits-Client/OperationalBITS transfer started (59, includes URL) / stopped (60, URL + status code). Job creation is event 3.Malware download via bitsadmin. T1197

Defense Evasion & Log Tampering

IDLogMeaning / investigative valueNotes · ATT&CK
1102SecuritySecurity log cleared — records who did it. Cannot be suppressed while auditing runs.Almost never legitimate T1685.005
1100SecurityEvent 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
104SystemAny event log cleared (System-side record, names the log).T1685.005
4719SecuritySystem audit policy changed — auditing dialed down before action.T1685.001
1116 / 1117Windows Defender/OperationalMalware detected / action taken — name, path, hash, user.Detection history even if later disabled.
5001 / 5007Windows Defender/OperationalReal-time protection disabled / config changed (exclusions added).Exclusion paths reveal attacker working dirs T1685
2004 / 2005 · 2071 / 2097Windows Firewall/FirewallFirewall 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.

  1. Start at Sysmon 1: command line, parent, user, hashes and a ProcessGuid that stays unique even when Windows reuses the PID.
  2. 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.
  3. 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.

Where Sysmon sees each stage of an intrusion Sysmon 1, process creation, is the spine: it records the command line, parent, user, hashes and the ProcessGuid that later events point back to. Six stages follow. Delivery: Sysmon 22 DNS query, Sysmon 11 file written, Sysmon 15 Zone.Identifier stream, Sysmon 29 new executable on disk. Execution: Sysmon 7 DLL load, off by default; Sysmon 8 remote thread; Sysmon 25 process image replaced. Persistence: Sysmon 13 registry value set, Sysmon 20 WMI consumer, with 19 and 21, Sysmon 6 driver load. Credential access: Sysmon 10 process access to LSASS, Sysmon 9 raw disk read. Lateral movement and C2: Sysmon 3 network connection, off by default; Sysmon 17 pipe created; Sysmon 18 pipe connected. Evasion: Sysmon 2 file creation time changed, Sysmon 26 file deleted, Sysmon 16 Sysmon config changed, Sysmon 4 Sysmon service stopped. Sysmon 1 process create: command line, parent, user, hashes and the ProcessGuid every box below points back to 1Delivery Sysmon 22DNS query by process Sysmon 11file written Sysmon 15Zone.Identifier URL Sysmon 29new PE on disk 2Execution Sysmon 7DLL load / side-load Sysmon 8remote thread Sysmon 25image replaced · hollow 7 is off by default 3Persistence Sysmon 13Run key / service value Sysmon 20WMI consumer (+ 19, 21) Sysmon 6driver load · BYOVD 4Credentials Sysmon 10LSASS opened Sysmon 9raw disk read (NTDS.dit) read GrantedAccess + CallTrace 5Lateral · C2 Sysmon 3outbound connection Sysmon 17pipe created (server) Sysmon 18pipe connected (client) 3 is off by default 6Evasion Sysmon 2creation time reset Sysmon 26file deleted Sysmon 16Sysmon config changed Sysmon 4Sysmon service stopped Join every box to the spine on ProcessGuid (8 and 10 carry SourceProcessGuid and TargetProcessGuid). 3 and 7 are off by default; 10, 11 and 22 are usually narrowed. Read the config before reading absence.
Fig. — Where Sysmon sees each stage of an intrusion, joined on ProcessGuid.

Event reference

IDEvent (config tag)Key fields · investigative valueNotes · ATT&CK
Sysmon 1Process 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 2File 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 3Network 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 4Sysmon service state changedThe Sysmon service started or stopped (State).Cannot be filtered. Stopped outside maintenance = possible tampering. T1685.001
Sysmon 5Process terminated (ProcessTerminate)UtcTime, ProcessGuid, ProcessId — bounds a process's lifetime with Sysmon 1.Often excluded to save volume.
Sysmon 6Driver 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 7Image 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 8CreateRemoteThreadA 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 9RawAccessReadA 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 10ProcessAccessA 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 11File 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 12Registry 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 13Registry 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 14Registry key / value renamed (RegistryEvent)TargetObject and NewName: a key or value renamed into place.Rare; worth reviewing on servers.
Sysmon 15File 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 16Sysmon configuration changeThe 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 17Named pipe created (PipeEvent)PipeName and the server process.C2 frameworks and remote-exec tools use default pipe names (see detection rules).
Sysmon 18Named pipe connected (PipeEvent)PipeName and the client process.Pair 17 (server) with 18 (client) to see who talks to whom.
Sysmon 19WMI event filter (WmiEvent)WMI event filter registered: namespace, name and WQL query (the trigger).T1546.003
Sysmon 20WMI 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 21WMI 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 22DNS 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 23File 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 24Clipboard 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 25Process 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 26File delete logged (FileDeleteDetected)File deleted; logged without keeping a copy.The cheaper default for deletion logging. T1070.004
Sysmon 27File 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 28File block shredding (FileBlockShredding)Sysmon blocked file shredding by tools such as SDelete.Protects evidence from wiping. T1485
Sysmon 29FileExecutableDetectedA 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 255ErrorInternal 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

CheckWhere / commandWhat it tells youNotes
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 imageC:\Windows\System32\winevt\Logs\Microsoft-Windows-Sysmon%4Operational.evtx · SYSTEM hive ControlSet00x\Services\<driver>\ParametersThe 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 -cWith no file argument, prints the active config.Read excludes before concluding "no evidence".
Config and service historySysmon 16 · Sysmon 4 · Sysmon 255When 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.
Collectwevtutil epl Microsoft-Windows-Sysmon/Operational C:\IR\sysmon.evtx · \Sysmon\ archiveExport 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.
ParseEvtxECmd (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.
PivotProcessGuid · ParentProcessGuid · LogonIdBuild the process tree across 1, 3, 7, 11, 22 and more; LogonId joins to Security 4624.PIDs are reused; GUIDs are not.
Two timestampsUtcTime vs the event's TimeCreatedUtcTime 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)

DetectionSysmon event · logicWhy · false positivesSigmaHQ · ATT&CK
LSASS memory accessSysmon 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.dllSysmon 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 LOLBinSysmon 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 PowerShellSysmon 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 downloadSysmon 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 executionSysmon 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 shellSysmon 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 injectionSysmon 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 hollowingSysmon 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-loadingSysmon 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 pipesSysmon 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 subscriptionSysmon 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 keysSysmon 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 folderSysmon 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 pathsSysmon 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 ADSSysmon 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 internetSysmon 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 DNSSysmon 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 accessSysmon 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 executableSysmon 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 unloadSysmon 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 / attackWhat happensWhat still catches itATT&CK
Readable exclusionsAdmins 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 unloadedfltmc 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 swappedsysmon64 -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 blindedTools 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 clearedwevtutil 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 spoofingA 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 clientDoH 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 narrowed3 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

StepCommand / settingWhat it doesNotes
Install (standalone)sysmon64 -accepteula -i config.xmlInstalls 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 configsysmon64 -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 / uninstallsysmon64 -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 archiveArchiveDirectory · CopyOnDeletePE · CopyOnDeleteExtensions · CopyOnDeleteSIDs · CopyOnDeleteProcessesControls what Sysmon 23 keeps; default folder Sysmon at each volume root, SYSTEM-only.Use 26 instead of 23 where disk space matters.
Other entriesCheckRevocation · DnsLookup · DriverNameSignature 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 configsSwiftOnSecurity sysmon-config · Olaf Hartong sysmon-modular · Neo23x0 sysmon-config forkCommented 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.
TuningBusiest: 3, 7, 10, 11, 12 / 13, 22Exclude 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 & forwardingwevtutil gl Microsoft-Windows-Sysmon/Operational · wevtutil sl Microsoft-Windows-Sysmon/Operational /ms:1073741824Check 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 scaleGPO, 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.
ValidateAtomic 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.

Logon types grouped by route Logon types grouped by how the session starts: at the keyboard (2 Interactive, 7 Unlock, 11 CachedInteractive), network (3 Network, 8 NetworkCleartext), remote desktop (10 RemoteInteractive), system started (4 Batch, 5 Service) and alternate credentials (9 NewCredentials). Colour shows the credentials-in-memory column of the table. how the session starts Outline: green = creds not cached (usually) · red = password exposed · grey = Yes or — in the table at keyboard2 Interactive7 Unlock11 CachedInteractive network3 Network8 Cleartext remote desktop10 RemoteInteractiveRDP / TS system started4 Batch5 Service alt creds9 NewCredentialsrunas /netonly 8: password exposed Conceptual grouping built only from the table below. Fast wins: Type 10 to servers · Type 3 bursts from one source · Type 9 anywhere.
Fig. — Logon types grouped by route, coloured by credential exposure.
TypeNameWhat it means in practiceCreds cached in memory?
2InteractiveConsole logon at the keyboard (or via KVM).Yes
3NetworkSMB shares, WMI, WinRM, most lateral movement (destination side).No (usually)
4BatchScheduled task execution.Yes
5ServiceService start under a configured account.Yes
7UnlockWorkstation unlocked — user was physically present again.—
8NetworkCleartextNetwork logon with cleartext creds (IIS basic auth, some PowerShell).Password exposed in memory
9NewCredentialsrunas /netonly — alternate creds for outbound only. Common in attacker tradecraft (e.g. overpass-the-hash).Yes (alternate set)
10RemoteInteractiveRDP / Terminal Services.Yes
11CachedInteractiveLogon with cached domain creds (DC unreachable) — e.g. laptop off-network.—
12CachedRemoteInteractiveRDP logon validated with cached credentials (Microsoft: “same as RemoteInteractive”).—
13CachedUnlockWorkstation unlock validated with cached credentials.—
Triage heuristic: unexpected Type 10 to servers, Type 3 bursts across many hosts from one source, and Type 9 anywhere are the three fastest wins.

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
  1. 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).
  2. TGS exchange — the client presents the TGT and asks for a ticket to one service (SPN). The DC logs 4769.
  3. 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).

Kerberos ticket flow with event IDs Client sends AS-REQ to the DC and receives a TGT, logged as 4768. Client sends TGS-REQ and receives a service ticket, logged as 4769. Client presents the service ticket to the service, which logs 4624 type 3. CLIENT DC / KDC SERVICE HOST 1 AS-REQ (user + pre-auth) AS-REP → TGT 4768TGT requested fail → 4771 (0x18 = bad password) 2 TGS-REQ (TGT + SPN) TGS-REP → service ticket 4769service ticket requested RC4 (0x17) + many SPNs = Kerberoasting renewal → 4770 3 AP-REQ (service ticket) 4624type 3 on target Silver Ticket: DC never sees this hop Golden Ticket: forged TGT → 4769 with no 4768
Where the logs live: steps 1–2 are logged on the DC (Security log); step 3 is logged on the target host.

Ticket & Auth Events

IDMeaningKey fieldsNotes · ATT&CK
4768TGT 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.
4769Service 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
4770Service ticket renewed.Account, Service Name.Long-lived sessions.
4771Pre-authentication failed (AS-REQ rejected).Account, Client IP, Failure Code (0x18 = bad password), Pre-Auth Type.Kerberos brute force. T1110
4772 / 4773TGT request failed / service ticket request failed.Failure code.Rarer; enrich 4771/4769 failures.
4768 no pre-authTGT 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)

CodeMeaningInvestigative read
0x0Success.—
0x6Client not found (username doesn't exist).User enumeration / typo'd sprays.
0x7Server/SPN not found in database.SPN scanning.
0x9Client 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.
0x12Account disabled, expired, locked out, or logon-hours restricted.Access to a dormant/disabled account being attempted.
0x17Password has expired.—
0x18Pre-auth failed — wrong password (account is valid & enabled).The password-spray / brute-force signature
0x19Additional 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.
0x1FIntegrity check on decrypted field failed.Possible ticket tampering.
0x20Ticket 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.
0x25Clock skew too great.Replay tooling / misconfigured attacker host.

Encryption Types (Ticket Encryption Type field)

ValueCipherInvestigative read
0x1 / 0x3DES-CBC-CRC / DES-CBC-MD5.Legacy, broken — should never appear on a modern domain
0x11 / 0x12AES128 / AES256 (HMAC-SHA1).Normal for current domains.
0x17RC4-HMAC.Downgrade tell: RC4 on a 4769 where AES is expected = Kerberoasting / overpass-the-hash
0x18RC4-HMAC-EXP (export).Very suspicious — forced weak downgrade.
0xFFFFFFFFUnknown/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)

IDMeaningWhereNotes · ATT&CK
System 201–209KDCSVC 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 onKDC 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

AttackWhat to look forWhere · ATT&CK
KerberoastingOne 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 Roasting4768 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 TicketForged 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 TicketForged 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-TicketSame 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-HashNTLM hash used to request a Kerberos TGT — often surfaces as RC4 (0x17) 4768 from a workstation using tooling.4768 encryption downgrade + 4648. T1550.002
DCSyncReplication (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 TicketVariants 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 CredentialsAttacker 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 abuseACE 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 KeyMaster 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.

  1. 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.
  2. 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.
  3. BadSuccessor (dMSA abuse) — on Windows Server 2025, create a delegated Managed Service Account in an OU (5137), then set msDS-ManagedAccountPrecededByLink to a privileged account and msDS-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.

Directory-object abuse: where the evidence appears Shadow Credentials: a write to msDS-KeyCredentialLink is logged as 5136, then PKINIT authentication is logged as 4768 with certificate info. AdminSDHolder: an ACE added to the AdminSDHolder object is logged as 5136, then SDProp copies it to protected accounts and groups about every 60 minutes. BadSuccessor on Windows Server 2025: creating a delegated Managed Service Account in an OU is logged as 5137, linking it to a privileged account through msDS-ManagedAccountPrecededByLink and msDS-DelegatedMSAState = 2 is logged as 5136, and the KDC then issues the dMSA a TGT with the target's privileges, logged as 2946 in the Directory Service log on the DC. Before the August 2025 fix (CVE-2025-53779) create rights on an OU were enough; after it, control of the target account is still abusable. SHADOW CREDENTIALS write msDS-KeyCredentialLinkattacker adds a key credential PKINIT (certificate) authas the target account NT hash recoveredno password change = quiet 5136DS Changes on the attribute 4768with certificate info ADMINSDHOLDER / SDPROP ACE added to AdminSDHolderCN=AdminSDHolder,CN=System SDProp runsabout every 60 min ACE copied to allprotected accounts/groups 5136on AdminSDHolder (needs SACL / DS Changes auditing) BADSUCCESSOR (dMSA, WINDOWS SERVER 2025) create a dMSA in an OUCreateChild on the OU link it to a Domain AdminmsDS-DelegatedMSAState = 2 KDC issues the dMSA a TGTwith the target's privileges 5137dMSA object created (SACL) 5136PrecededByLink / MSAState 2946Directory Service log, DC pre-fix (CVE-2025-53779): OU create rights alone were enough; post-fix, control of the target is still abusable
Directory-change evidence (5136, and 5137 for a new dMSA) comes before any authentication evidence; see the Shadow Credentials, BadSuccessor and AdminSDHolder rows above.
▸ 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.

  1. 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.
  2. 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; an nTDSDSA object, 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.

SID History and DCShadow: which path leaves which event Top lane: SID History written on a domain controller is logged as 4765 when added, 4766 when the attempt fails, and 4738 for the changed account; outside a planned migration any of these is suspicious. Middle lane: DCShadow registers a host as a domain controller, seen as 4742 or 5136 for a GC SPN on a non-DC and 5137 for an nTDSDSA object; it pushes the change by replication, seen as 4928 and 4929 and replication RPC from a non-DC; it then unregisters, seen as 5141, and a DRS SPN may be left behind. The pushed change writes no 4765 and the usual 5136 can be missing. Bottom lane: checking the directory itself with Get-ADObject for sIDHistory and repadmin /showobjmeta catches both paths. SID HISTORY WRITTEN ON A DC (e.g. a domain migration) SID added to sIDHistorythrough the directory, on a DC the DC logs itAudit User Account Management logons carry the added SIDand the rights that come with it Subject = who did it Target / Source Account, SID List 4765SID History added 4766attempt failed 4738account changed (new value) outside a planned migration: suspicious; worst case RID 512 / 519 or a same-domain SID DCSHADOW (ROGUE DC) — THE SAME CHANGE, PUSHED BY REPLICATION register a host as a DCnTDSDSA object + GC/, DRS SPNs push the change to a real DCSID History, ACLs, credentials unregisterrogue DC objects removed 4742or5136GC/ SPN on a non-DC 5137nTDSDSA object created 4928replica source NC established 4929replica source NC removed replication RPC from a non-DC 5141nTDSDSA object deleted a DRS SPN left on a non-DC arrives as replication: no 4765, and the usual 5136 for the change can be missing CHECK THE DIRECTORY ITSELF (catches both paths, and offline ntds.dit edits) Get-ADObject "(sIDHistory=*)"every account that has SID History repadmin /showobjmetaoriginating DSA that is not a known DC
The logged path (4765 / 4766 on the DC, see Windows Event IDs by Scenario › Account & Group Manipulation) and the silent one (DCShadow, see the row above) end in the same directory state, so the sweep at the bottom is the check that does not depend on audit policy.
Fastest Kerberos wins: (1) RC4 (0x17) tickets where the domain runs AES; (2) 4769 events with no correlating 4768 (forged TGT); (3) 4662 replication requests from anything that isn't a DC (DCSync).

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.

ArtifactLocationWhat it provesCaveats · Tool
PrefetchC:\Windows\Prefetch\*.pfProgram 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\AppCompatCacheExecutable existed on disk with its path + last-modified time; ordering hints at recency.Win10+: presence ≠ execution. Written only at shutdown/reboot · AppCompatCacheParser
AmcacheC:\Windows\AppCompat\Programs\Amcache.hveSHA-1 hash of file, full path, size, compile time, first-seen time. Hash even for deleted binaries.Entry ≠ execution on modern builds · AmcacheParser
UserAssistNTUSER.DAT\...\CurrentVersion\Explorer\UserAssist\{GUID}\CountGUI-launched programs per user: run count, last run, focus time. Values ROT13-encoded.CLI launches not recorded · RECmd / Registry Explorer
BAM / DAMSYSTEM\...\Services\bam\State\UserSettings\{SID}Last execution time of programs per user SID (Background Activity Moderator).Win10 1709+ · ~1 week retention typical · RECmd
SRUMC:\Windows\System32\sru\SRUDB.datPer-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 ListsC:\Users\<u>\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations\*.automaticDestinations-msApp (by AppID) + documents opened with it, MRU timestamps. First/last-used times per item.JLECmd
MUICacheUsrClass.dat\Local Settings\Software\Microsoft\Windows\Shell\MuiCachePrograms run per user (friendly-name cache) — survives even for deleted EXEs.No timestamps · RECmd
RunMRUNTUSER.DAT\...\Explorer\RunMRUCommands typed in Start→Run.Ordered by MRU list, no per-item time · RECmd
CapabilityAccessManagerNTUSER.DAT & SOFTWARE\...\CapabilityAccessManager\ConsentStoreLast time apps used mic/camera — anchors interactive sessions.Win10 1903+ · RECmd
Windows TimelineC:\Users\<u>\AppData\Local\ConnectedDevicesPlatform\*\ActivitiesCache.dbApp 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.txtEvery 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.txtWindows 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-*.logPlain-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:

  1. 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.
  2. Read the two kinds of line. An EstimatedImpact line names a ProcessImageName with scan Count / TotalTime / MaxTime: the process ran and touched files. A detection line records the file, the process and the Defender threat name.
  3. 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.
What Defender's MPLog keeps when the usual execution trail is gone Top: when Prefetch has been wiped or disabled, the event logs cleared and the binary deleted, the Defender protection log often survives: plain-text MPLog-*.log files under C:\ProgramData\Microsoft\Windows Defender\Support, read with grep or a community parser. First move: string-search it for the tool name, for example rclone.exe. Middle: two kinds of line matter. EstimatedImpact lines name a ProcessImageName with scan Count, TotalTime and MaxTime, which shows the process ran and touched files. Detection lines record the file, the process and the Defender threat name. Bottom: 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 and 1117 and the Defender Quarantine. ATT&CK T1204.002. 1 · WHEN THE USUAL TRAIL IS GONE · MPLog often survives PREFETCH wiped or disabled EVENT LOGS cleared THE BINARY deleted DEFENDER MPLog C:\ProgramData\Microsoft\Windows Defender\ Support\MPLog-*.log plain text · grep or a community parser first move: string-search MPLog for the tool name, e.g.rclone.exe 2 · TWO KINDS OF LINE TO LOOK FOR PERFORMANCE LINE EstimatedImpact ProcessImageName scan Count · TotalTime · MaxTime → the process ran and touched files DETECTION LINE the file the process the Defender threat name → what Defender flagged, and in which process 3 · WEIGH IT, THEN CORROBORATE troubleshooting log, not a documented audit trail entries are not complete how far back it reaches varies COLLECT IT TOGETHER WITH Defender event logs: 1116 detected · 1117 action taken Defender Quarantine a missing entry does not prove the tool never ran · ATT&CK T1204.002 Defender protection log (MPLog)the row this figure is built from 1116 / 1117Defender detections in Windows Defender/Operational Prefetchthe first execution artifact to check, when it is still there
Built from the Defender protection log (MPLog) row above, with the 1116 / 1117 Defender rows in Event IDs and the Prefetch row.

File & Folder Knowledge

ArtifactLocationWhat it provesCaveats · Tool
LNK filesC:\Users\<u>\AppData\Roaming\Microsoft\Windows\Recent\*.lnkFile/folder was opened: target path, target MAC times, size, volume serial + type (fixed/removable), hostname.Auto-created on open; survives target deletion · LECmd
ShellbagsUsrClass.dat\Local Settings\Software\Microsoft\Windows\Shell\BagMRU & BagsFolders browsed in Explorer — including deleted folders, removable media, network shares, zips.Proves folder knowledge, not file opens · SBECmd / ShellBags Explorer
OpenSavePidlMRUNTUSER.DAT\...\ComDlg32\OpenSavePidlMRUFiles opened/saved via common dialog boxes, per extension.RECmd
LastVisitedPidlMRUNTUSER.DAT\...\ComDlg32\LastVisitedPidlMRUWhich application used the dialog + last directory per app.RECmd
RecentDocsNTUSER.DAT\...\Explorer\RecentDocsRecently opened documents per extension, MRU-ordered.Key write time = most recent item only · RECmd
Office MRU / Reading LocationsNTUSER.DAT\Software\Microsoft\Office\<ver>\<app>\File MRUOffice docs opened with precise timestamps; Reading Locations shows last edit position.Includes docs on removable/cloud paths · RECmd
Trusted DocumentsNTUSER.DAT\Software\Microsoft\Office\...\Security\Trusted Documents\TrustRecordsUser clicked Enable Editing / Enable Content — macro doc executedPhishing-origin goldmine · RECmd
WordWheelQueryNTUSER.DAT\...\Explorer\WordWheelQueryTerms typed into Explorer/Start search — intent evidence.RECmd
ThumbcacheC:\Users\<u>\AppData\Local\Microsoft\Windows\Explorer\thumbcache_*.dbThumbnails of viewed images/docs — survives original deletion.Thumbcache Viewer
$Recycle.BinC:\$Recycle.Bin\<SID>\$I...... / $R......$I = original path + deletion time; $R = the content itself.Shift+Delete bypasses · RBCmd
$MFTVolume 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\$UsnJrnlJournal 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)
$LogFileVolume 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 CopiesSystem Volume InformationPoint-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.

Persistence locations grouped by startup stage Three columns: boot (services, print processors, LSA packages), logon (Winlogon, Run keys, Startup folders, logon scripts) and on-trigger (scheduled tasks, WMI, IFEO, AppInit_DLLs, COM hijacking, Netsh helpers, accessibility hijack). AT BOOT · SYSTEM CONTEXT Services Print processors / ports LSA / SSP packages AT LOGON · USER CONTEXT Winlogon (Userinit, Shell) Run / RunOnce keys Startup folders Logon scripts ON TRIGGER · WHEN SOMETHING RUNS Scheduled tasks WMI event subscriptions IFEO debugger AppInit_DLLs COM hijacking Netsh helper DLLs Accessibility hijack Boot → logon → on-demand. Sweep all three stages: a persistence item can live at any of them.
Fig: persistence mechanisms from the table below, grouped by when they execute.
MechanismLocationWhat to look forATT&CK
Run / RunOnce keysHKLM & HKCU\Software\Microsoft\Windows\CurrentVersion\Run, RunOnce (+ Wow6432Node)Entries pointing at user-writable paths (AppData, Temp, Public), script hosts, LOLBins.T1547.001
ServicesHKLM\SYSTEM\CurrentControlSet\Services\<name>ImagePath in odd dirs, svchost -k with rogue ServiceDll, recently created keys.T1543.003
Scheduled tasksC:\Windows\System32\Tasks\ (XML) + HKLM\...\Schedule\TaskCacheTasks with Actions pointing at temp paths; hidden tasks (registry entry without XML or SD value removed).T1053.005
Startup foldersC:\Users\<u>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup & ProgramData equivalentAny unexpected LNK/EXE/script.T1547.001
WMI event subscriptionsC:\Windows\System32\wbem\Repository\OBJECTS.DATA__EventFilter + CommandLineEventConsumer + __FilterToConsumerBinding triples. Also event 5861.T1546.003
WinlogonHKLM\Software\Microsoft\Windows NT\CurrentVersion\WinlogonUserinit ≠ userinit.exe alone; Shell ≠ explorer.exe.T1547.004
IFEO debuggerHKLM\...\Image File Execution Options\<exe>"Debugger" value hijacks launch of the named EXE (classic: sethc.exe → cmd.exe).T1546.012
Accessibility hijackC:\Windows\System32\sethc.exe, utilman.exe, osk.exeHash mismatch vs known-good / replaced binaries → SYSTEM shell at lock screen.T1546.008
AppInit_DLLsHKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLsAny DLL listed loads into every user-mode process.T1546.010
LSA / SSP packagesHKLM\SYSTEM\CurrentControlSet\Control\Lsa (Security Packages, Authentication Packages)Rogue SSP DLL harvests plaintext creds at logon.T1547.005
Netsh helper DLLsHKLM\Software\Microsoft\NetshAdded helper DLL loads when netsh runs.T1546.007
COM hijackingHKCU\Software\Classes\CLSID\{...}\InprocServer32Per-user CLSID overriding an HKLM class → DLL loads into trusted processes.T1546.015
Logon scriptsHKCU\Environment\UserInitMprLogonScript · GPO Scripts\ dirsScript path set where none expected.T1037
Print processors / portsHKLM\SYSTEM\CurrentControlSet\Control\Print\Environments\...\Print ProcessorsRogue DLL loaded by spooler as SYSTEM.T1547.012
Password filter DLLHKLM\SYSTEM\CurrentControlSet\Control\Lsa → Notification Packages (REG_MULTI_SZ, DLL names without .dll) · the DLL sits in C:\Windows\System32LSA 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:

  1. What a filter sees. On a password change or reset, LSA asks each DLL in Notification Packages to validate the new password and passes it the plaintext: local accounts on any host, domain accounts on a domain controller.
  2. Check the value. Read HKLM\SYSTEM\CurrentControlSet\Control\Lsa → Notification Packages (REG_MULTI_SZ, names without .dll). The default is usually only scecli. Any other name, unless it is a known password-policy or sync product, is a lead: find the DLL in C:\Windows\System32, hash it and check its signer.
  3. 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.
What a password filter DLL sees, and how to check one Top: a password change or reset, for a local account or, on a domain controller, a domain account, goes to LSA, which asks every DLL listed in the Notification Packages value under HKLM\SYSTEM\CurrentControlSet\Control\Lsa to validate it and passes each one the new password in plaintext. On a DC a rogue filter captures every password change, including the resets made during remediation. Middle: read Notification Packages, a REG_MULTI_SZ of DLL names without .dll. The default is usually only scecli, or a known password-policy or sync product. Any other name is a lead: find the DLL in C:\Windows\System32, hash it and check its signer. Bottom: registration takes effect only after LSA restarts, at reboot; check the value on every DC before resetting passwords. The same key holds the Security Packages and Authentication Packages values of the LSA / SSP packages row. Related rows: 4723 / 4724 password change and reset events, and Sysmon 13 registry value set. ATT&CK T1556.002. 1 · WHAT A FILTER SEES · every new password, in plaintext PASSWORD CHANGE / RESET local account on a DC: domain account LSA asks every registered filter to validate it EACH DLL IN Notification Packages HKLM\SYSTEM\CurrentControlSet\Control\Lsa receives the new password in plaintext on a DC a rogue filter captures every password change, including the resets made during remediation 2 · CHECK THE VALUE READ Notification Packages REG_MULTI_SZ DLL names without .dll → list every name EXPECTED usually onlyscecli or a known password-policy or password-sync product → expected entry ANY OTHER NAME IS A LEAD find the DLL in C:\Windows\System32 hash it · check its signer → a lead until the DLL checks out 3 · TIMING, AND WHAT TO DO FIRST WHEN IT STARTS WORKING registration takes effect only after LSA restarts (a reboot) BEFORE YOU RESET PASSWORDS check Notification Packages on every DC a rogue filter would capture the new ones too same Lsa key, other values: Security Packages / Authentication Packages (logon-time SSPs) · ATT&CK T1556.002 Password filter DLLthe row this figure is built from LSA / SSP packagesrogue SSP DLL harvests plaintext credentials at logon (same key) 4723 / 4724password change / reset: the changes a filter was handed Sysmon 13registry value set: the write to Notification Packages, if the config covers it
Built from the Password filter DLL row above, with the LSA / SSP packages row and the 4723 / 4724 and Sysmon 13 rows in Event IDs and Sysmon.

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: $SI versus $FN and independent sources An $MFT record holds $STANDARD_INFORMATION, which tools alter, and $FILE_NAME, which the kernel updates. A mismatch between them signals timestomping. $UsnJrnl and other artifacts provide independent corroboration. $MFT record (one per file) $STANDARD_INFORMATION user-visible timestamps altered by timestomp tools e.g. whole seconds (.0000000) $FILE_NAME kernel-maintained timestamps updated on rename/move tools usually leave it alone mismatch / impossible order = forged Independent corroboration $UsnJrnl:$J — real sequence Prefetch · Amcache · LNK Event logs · $LogFile · SRUM real activity window still shows Row-by-row detection detail is in the Timestomping table below. Layout is conceptual, not a byte-level $MFT map.
Fig. — Timestomping: where the lie lives ($SI) and where the truth survives ($FN, journal, other artifacts).

Timestomping (Timestamp Manipulation)

Technique / artifactWhere to checkDetectionNotes · ATT&CK
$SI vs $FN mismatch$MFT — $STANDARD_INFORMATION vs $FILE_NAME timestampsTools 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:$JJournal 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 sourcesPrefetch, Amcache, event logs, LNK, $LogFile, SRUMThe 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

TechniqueWhere to checkDetectionNotes · ATT&CK
Full log clearSecurity 1102 · System 104The 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 stoppedSystem 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 downgradeSecurity 4719 · 4907Auditing disabled for a subcategory right before activity — explains "missing" events.T1685.001
Log source redirection / sizeHKLM\SYSTEM\...\Services\EventLog\<log> (MaxSize, Retention)Max size shrunk to force rapid rollover, flushing evidence.Check MaxSize vs defaults.
ETW patchingSysmon 7 (image loads), memoryIn-process EtwEventWrite patch blocks telemetry from that process only — host still logs normally elsewhere.malfind + compare expected vs actual telemetry. T1685
ATT&CK v19 remappingDetection rules, reports and tags referencing “Defense Evasion”, T1562.x or T1070.001 / .002ATT&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

TechniqueWhere to checkDetectionNotes · ATT&CK
Shadow copy deletionSecurity 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 fsutilfsutil usn deletejournal wipes the change journal; the command still hits process-creation logs.A suddenly tiny/reset journal is itself suspicious.
Prefetch disabled/wipedHKLM\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, Prefetchsdelete/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 StreamsNTFS 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 flagsFiles flagged Hidden+System to drop out of default listings; MFT ignores the flags.T1564.001
File renaming / masqueradeAmcache (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, VSSShift+Delete leaves no $I/$R but the deletion is journaled and prior versions persist in shadow copies.Recover via VSS.
History/artifact clearingPSReadLine history, browser DBs, ~/.bash_historyCleared or truncated files — recover deleted SQLite rows (WAL/free pages) and carve from unallocated space.Empty history + heavy activity elsewhere = red flag.
Time/NTP manipulationSystem 1 (Kernel-General) time-change · Security 4616System clock rolled to skew all subsequent timestamps.4616 by a user process (not w32time) = deliberate T1070.006
Registry key backdatingHive last-write times vs transaction logsKey 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.
Golden rule: when one source is silent, the destruction usually generates its own evidence — a clear event (1102/104), a process-creation record for the wiping command, a journal entry, or an untouched redundant artifact. Always build a timeline from at least two independent sources before trusting any single timestamp.

Network, Browser & USB Activity

ArtifactLocationWhat it provesCaveats · Tool
SRUM network dataC:\Windows\System32\sru\SRUDB.dat (Network Data Usage table)Bytes sent/received per app per hour — quantify exfiltration by process.SrumECmd
Chrome / Edge historyC:\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 historyC:\Users\<u>\AppData\Roaming\Mozilla\Firefox\Profiles\*\places.sqliteURLs + downloads (moz_annos).
WebCacheV01.datC:\Users\<u>\AppData\Local\Microsoft\Windows\WebCache\WebCacheV01.datIE/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.IdentifierZoneId=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 WebDAVOpening 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 MRUNTUSER.DAT\Software\Microsoft\Terminal Server Client\Servers & DefaultHosts this user RDP'd out to (+ username hint per host) — outbound lateral movement from a compromised box.RECmd
RDP bitmap cacheC:\Users\<u>\AppData\Local\Microsoft\Terminal Server Client\Cache\*.bmc, cache*.binScreen-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, *.jrsWindows 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 profilesSOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\ProfilesNetworks/SSIDs joined with first/last connect times, gateway MAC — geolocate a laptop's movement.RECmd
USB — USBSTORSYSTEM\CurrentControlSet\Enum\USBSTOR & USBVendor/product/serial of every mass-storage device ever connected.Correlate serial across hosts · RECmd
USB — first/last connectsetupapi.dev.log (C:\Windows\INF\) + Enum key timestampsFirst install time (setupapi) and last-connect times (key write times, 0064/0066/0067 properties).
USB — drive letter mapSYSTEM\MountedDevices + SOFTWARE\...\EMDMgmt (pre-Win10) + Windows Portable DevicesWhich serial got which letter / volume name — link LNK volume serials to a specific stick.
USB — user linkageNTUSER.DAT\...\Explorer\MountPoints2\{GUID}Which user actually used the mounted volume.GUID matches MountedDevices
DNS client cache logMicrosoft-Windows-DNS-Client/Operational (if enabled) · Sysmon 22Hostname resolutions by process.Off by default
hosts fileC:\Windows\System32\drivers\etc\hostsMalicious 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:

  1. USB — USBSTOR: which device. Vendor, product and serial number; the serial is the thread through everything else.
  2. 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.
  3. 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.
  4. 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.

USB device trail: device, time, volume, user, then files opened Four questions answered in order, each handing a key to the next. One, which device: USB — USBSTOR, SYSTEM\CurrentControlSet\Enum\USBSTOR and USB, vendor, product and serial of every mass-storage device. Two, when: USB — first/last connect, setupapi.dev.log for the first install and the Enum key timestamps and 0064/0066/0067 properties for connect times. Three, which letter and volume: USB — drive letter map, SYSTEM\MountedDevices plus EMDMgmt before Windows 10 and Windows Portable Devices, which serial got which letter and volume name. Four, which user: USB — user linkage, the MountPoints2 GUID in NTUSER.DAT that matches MountedDevices. From the volume serial and the user's hives, what was opened from the stick: LNK files with volume serial and removable drive type, Shellbags for folders browsed on removable media, Office MRU for documents on removable paths, and PCA for GUI programs launched from removable media on Windows 11 22H2 and later. Correlate the serial across hosts. 1 · WHICH DEVICEUSB — USBSTOR SYSTEM\CurrentControlSet\Enum\USBSTOR & USB vendor / product / serial of every mass-storage device same device serial 2 · WHENUSB — first/last connect setupapi.dev.log (C:\Windows\INF\) + Enum key timestamps first install (setupapi) · connects (0064/0066/0067) serial → drive letter / volume 3 · WHICH VOLUMEUSB — drive letter map MountedDevices + EMDMgmt (pre-Win10) + Portable Devices which serial got which letter / volume name volume {GUID} matches MountPoints2 4 · WHICH USERUSB — user linkage NTUSER.DAT\...\Explorer\MountPoints2\{GUID} which user actually used the mounted volume volume serial user's hives 5 · WHAT WAS OPENED FROM ITby volume serial or by user LNK files volume serial + type (removable) Recent\*.lnk · LECmd Shellbags folders browsed on removable media UsrClass.dat · SBECmd Office MRU / Reading Locations docs opened on removable paths NTUSER.DAT\...\Office · RECmd PCA (Program Compatibility Assistant) GUI launches from removable media Windows 11 22H2+ · appcompat\pca Correlate the device serial across hosts: the same stick on other machines widens the scope. Each step hands a key to the next: serial → drive letter / volume GUID → user → files.
Built from the USB rows above, plus the LNK files, Shellbags, Office MRU and PCA rows in File & Folder Knowledge and Evidence of Execution.

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.

Order of volatility: collection sequence Five steps from most to least volatile: 1 RAM, 2 network state (netstat, ARP, DNS cache), 3 running processes, 4 disk triage, 5 full image. most volatile — collect first least volatile — collect last 1 RAM live capture lost on shutdown 2 Network state netstat · ARP DNS cache 3 Processes running 4 Disk triage checklist table below 5 Full image disk image document hashes and times at every step (chain of custody) Sequence from the callout below the table. Conceptual diagram — adapt to the case.
Fig. — Order of volatility: memory and network state first, disk last.
EvidencePathContainsPriority
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.datUserAssist, RecentDocs, RunMRU, shellbags, MuiCache.P1
Event logsC:\Windows\System32\winevt\Logs\*.evtxEverything in the Event ID section. Security, System, PowerShell, Sysmon, TS-*, WMI, Defender first.P1
Filesystem metadata$MFT · $LogFile · $Extend\$UsnJrnl:$J · directory $I30 index slackFull file timeline incl. deleted files.P1
Prefetch + AmcacheC:\Windows\Prefetch\ · C:\Windows\AppCompat\Programs\Amcache.hveExecution evidence + hashes.P1
User artifactsC:\Users\*\AppData\Roaming\Microsoft\Windows\Recent\ (LNK + Jump Lists) · PSReadLine historyFile knowledge + typed commands.P1
SRUMC:\Windows\System32\sru\SRUDB.datApp + network usage history.P2
Scheduled tasks + WMI repoC:\Windows\System32\Tasks\ · C:\Windows\System32\wbem\Repository\Persistence configs.P2
Browser dataPer-browser paths aboveDelivery vector, exfil to web services.P2
Memory imageLive capture (winpmem / Magnet RAM Capture / DumpIt) · hiberfil.sys · pagefile.sys · MEMORY.DMPInjected code, C2 sockets, keys, console history — capture BEFORE shutdown.P1 (live)
Shadow copiesSystem Volume Information (vssadmin list shadows)Historical versions of all of the above.P3
Recycle BinC:\$Recycle.Bin\Deleted-but-recoverable content + deletion times.P2
Order of volatility: RAM → network state (netstat, ARP, DNS cache) → running processes → disk triage → full image. Document hashes and times for chain of custody as you collect.

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:

  1. pslist / pstree for the tree, then psscan — a process present in psscan but missing from pslist is unlinked or terminated.
  2. cmdline and netscan for what each suspicious process ran and talked to.
  3. malfind, dlllist / ldrmodules and handles for injection, unlinked DLLs and lsass access.
  4. 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.

Volatility 3 triage flow: processes, behaviour, injected code, then extract and scope Four stages in order. One, what is running: windows.pslist / pstree for the tree, then windows.psscan; a process in psscan but not in pslist is unlinked or terminated. Two, what it ran and talked to: windows.cmdline and windows.netscan / netstat. Three, what it loaded or injected: windows.malfind, windows.dlllist / ldrmodules, and windows.handles, where an lsass handle held by a non-system process indicates credential dumping. Four, extract and scope: windows.filescan + dumpfiles and windows.hashdump / lsadump / cachedump. At any time, cross-check memory against disk with windows.svcscan, windows.registry.hivelist / printkey and windows.modscan / driverirp. 1 · WHAT IS RUNNINGstart here: the process view windows.pslist / pstree wrong parent, misspelled names, user-writable paths windows.psscan in psscan but not pslist = unlinked or terminated 2 · WHAT IT RAN AND TALKED TOfor each suspicious process windows.cmdline encoded PowerShell, rundll32 / regsvr32 arguments windows.netscan / netstat external IPs from Office apps · svchost on odd ports 3 · WHAT IT LOADED OR INJECTEDcode that should not be there windows.malfind RWX private memory, MZ headers or shellcode windows.dlllist / ldrmodules False,False,False rows = unlinked DLLs windows.handles lsass handle from a non-system process = cred dumping 4 · EXTRACT AND SCOPEpull it out, size the exposure windows.filescan + dumpfiles recover tools never written stably to disk windows.hashdump / lsadump / cachedump SAM hashes, LSA secrets, cached domain creds ANY TIME · CROSS-CHECK MEMORY AGAINST DISK windows.svcscan memory-only service records windows.registry.hivelist / printkey Run keys as they were at capture windows.modscan / driverirp rootkit drivers, hooked IRPs Order is a habit, not a rule: follow whatever looks wrong into the next plugin.
Built from the Volatility 3 rows below: each plugin placed at the triage step where it answers a question.
PluginPurposeHunt notes
windows.pslist / pstreeRunning processes / parent-child tree.Wrong parent (e.g. lsass not under wininit), misspelled system names, user-writable paths.
windows.psscanScan for process objects incl. terminated/hidden.Diff vs pslist → unlinked (rootkit-hidden) processes.
windows.cmdlineCommand line per process.Encoded PowerShell, rundll32/regsvr32 arguments.
windows.netscan / netstatTCP/UDP endpoints with owning process.ESTABLISHED to external IPs from Office apps, svchost on odd ports.
windows.malfindInjected / RWX private memory regions.MZ headers or shellcode in unmapped regions; dump with --dump.
windows.dlllist / ldrmodulesLoaded modules / loader-list discrepancies.ldrmodules False,False,False rows = unlinked DLLs.
windows.handlesOpen handles per process.Handles to lsass.exe from non-system processes = cred dumping
windows.filescan + dumpfilesFile objects in memory; extract them.Recover attacker tools never written stably to disk.
windows.registry.hivelist / printkeyHives in memory; read keys live.Run keys as they were at capture time.
windows.hashdump / lsadump / cachedumpSAM hashes, LSA secrets, cached domain creds.Scope of credential exposure.
windows.svcscanServices from memory.Compare to registry — memory-only service records.
windows.modscan / driverirpKernel 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.

Linux persistence grouped by when it runs Four columns: at boot (systemd units, kernel modules, ld.so.preload, legacy rc.local and init.d scripts), at login or shell start (shell rc files, PAM modules, authorized_keys, XDG autostart entries on desktop systems, MOTD scripts that run as root at each SSH or console login), on trigger (cron, at jobs, systemd timers, udev rules, package manager hooks and maintainer scripts) and standing access (local accounts and UID 0, SUID and SGID binaries, sudoers drop-ins). AT BOOT Systemd units Kernel modules ld.so.preload rc.local / init.d legacy · see systemd ver AT LOGIN / SHELL Shell rc files PAM modules authorized_keys XDG autostart MOTD scripts XDG: desktop logins only ON TRIGGER Cron at jobs Systemd timers udev rules Package hooks STANDING ACCESS Local accts / UID 0 SUID / SGID sudoers drop-ins Boot → login → on-demand, plus access left behind. Check all four; one foothold rarely travels alone.
Fig: Linux Triage rows grouped by when they execute or what access they leave.
ArtifactLocation / commandWhat it provesCaveats
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 → lastlog2Login 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_historyCommands 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 --allService/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 --versionLegacy 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/unitsUserland rootkit — library injected into every dynamic binaryFile should normally not exist · T1574.006
PAM/etc/pam.d/* · /lib/security/ modulesBackdoored 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/tcpexe → "(deleted)" = running binary removed from disk; recover via cp /proc/PID/exe.Volatile — collect before shutdown
Audit log/var/log/audit/audit.log → ausearch -iSyscall-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 historySoftware 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-bootsUnified 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/shmDropped 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 testPersistence 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 -cAdded 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 moduleslsmod · /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 programsbpftool 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/passwdAdded 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 binariesfind / -xdev -type f \( -perm -4000 -o -perm -2000 \) -ls · verify with rpm -Va / debsums -cSetuid-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 --scriptsPersistence 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:

  1. Know what fires. APT reads every file in /etc/apt/apt.conf.d/: commands in APT::Update::Pre-Invoke run when package indexes are updated, and commands in DPkg::Pre-Invoke / DPkg::Post-Invoke run 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/.
  2. Sweep, then check who owns each file. grep -rn Invoke /etc/apt/apt.conf.d/ lists the APT hooks and rpm -qa --scripts prints the rpm scriptlets. Ask dpkg -S / rpm -qf which 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.
  3. 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 -i of a local .deb started by an unusual parent process (T1546.016).
  4. 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.
What package activity runs as root, and how to tell a hook from a backdoor Top: package activity (apt update, apt install, dpkg, rpm or dnf) runs two kinds of code as root. APT hooks in /etc/apt/apt.conf.d: APT::Update::Pre-Invoke runs when package indexes are updated, DPkg::Pre-Invoke and DPkg::Post-Invoke run around each dpkg call, each command through /bin/sh as root; on dnf hosts also sweep /etc/dnf/plugins. Maintainer scripts: preinst, postinst, prerm and postrm, for example /var/lib/dpkg/info/*.postinst, and rpm scriptlets listed by rpm -qa --scripts, run as root whenever the package is installed, upgraded or removed. Second: sweep with grep -rn Invoke /etc/apt/apt.conf.d/ and rpm -qa --scripts, then check ownership with dpkg -S or rpm -qf. Files owned by a package, or placed by config management (Puppet, Chef, Ansible) or a vendor agent, are the usual false positive; a hook nothing explains needs its command read. Third, red flags: an Invoke entry that calls curl or wget, a shell one-liner, a script in a temp or hidden directory, or dpkg -i of a local .deb from an unusual parent process; any of these is suspicious persistence and root execution (T1546.016). Bottom: time it with the package logs, the temp and staging directories and the audit log. 1 · WHAT RUNS AS ROOT WHEN PACKAGES CHANGE PACKAGE ACTIVITY apt update / apt install dpkg · rpm · dnf no new boot item: it runs on the next package run APT HOOKS · /etc/apt/apt.conf.d/* APT::Update::Pre-Invokeruns when package indexes are updated DPkg::Pre-Invoke / Post-Invokeruns around each dpkg call each command through /bin/sh as root · on dnf hosts also sweep /etc/dnf/plugins/ MAINTAINER SCRIPTS · preinst · postinst · prerm · postrm /var/lib/dpkg/info/*.postinstdpkg packages rpm -qa --scriptsrpm scriptlets run as root whenever the package is installed, upgraded or removed 2 · SWEEP, THEN ASK WHO OWNS EACH FILE SWEEP grep -rn Invoke /etc/apt/apt.conf.d/ rpm -qa --scripts WHO OWNS IT? dpkg -S <file> rpm -qf <file> a package, config management or a vendor agent nothing explains it: read the command usual false positive: Puppet, Chef, Ansible and vendor agents also drop apt.conf.d files 3 · COMMANDS THAT DO NOT BELONG IN A HOOK calls curl or wget a shell one-liner a script in a temp or hidden directory dpkg -i of a local .deb from an unusual parent any of these = suspicious: persistence and root execution on the next package run (T1546.016) 4 · THEN TIME IT Package logs when the hooks had a chance to run Temp & staging dirs the script a hook calls Audit log execve trail, if auditd rules cover it Package manager hooks & maintainer scripts the row this figure is built from
Built from the Package manager hooks & maintainer scripts, Package logs, Temp & staging dirs and Audit log rows above.
▸ 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:

  1. 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/motd is usually a symlink to it). A legitimate script is an NN-name file that only prints text.
  2. 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.
  3. 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.
  4. 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.
A login runs the MOTD scripts as root: how to tell a banner from a backdoor Top: any SSH or console login, by any user, starts pam_motd, which 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/motd is usually a symlink to it. Legitimate scripts are NN-name files that only print text, and a dropped script fires whenever anyone logs in. Second: sweep with ls -la --time-style=full-iso /etc/update-motd.d/, then check ownership with dpkg -S /etc/update-motd.d/*. A script owned by a package is compared with the package; a script no package owns needs reading. Packages, cloud images and container runtimes are the usual false positive. Third, red flags: a script that calls curl, wget or nc, a reverse shell, a file in a temp or hidden directory, or a MOTD run that spawns network tools or shells; any of these, or a script no package owns, is persistence that fires at every login (T1037). Bottom: time it with the auth logs, the temp and staging directories and the audit log. 1 · WHAT EVERY LOGIN RUNS AS ROOT ANY LOGIN SSH or console by any user PAM SESSION pam_motd runs at login RUN-PARTS · AS ROOT /etc/update-motd.d/* every executable script, in name order OUTPUT CACHED /run/motd.dynamic /etc/motd is usually a symlink to it expected: NN-name files that only print text · a dropped script fires whenever anyone logs in 2 · SWEEP, THEN ASK WHO OWNS EACH SCRIPT SWEEP · scripts and full timestamps ls -la --time-style=full-iso /etc/update-motd.d/ WHO OWNS IT? dpkg -S /etc/update-motd.d/* a package: compare with it no package owns it: read it usual false positive: packages, cloud images and container runtimes add legitimate scripts 3 · WHAT DOES NOT BELONG IN A MOTD SCRIPT calls curl, wget or nc a reverse shell a file in a temp or hidden directory a MOTD run that spawns network tools or shells any of these, or a script no package owns = persistence that fires at every login (T1037) 4 · THEN TIME IT Auth logs each login it records is one more run of every script Temp & staging dirs the files a script calls Audit log execve trail, if auditd rules cover it MOTD scripts (update-motd.d) the row this figure is built from
Built from the MOTD scripts (update-motd.d), Auth logs, Temp & staging dirs and Audit log rows above.

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.

  1. 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).
  2. Typed: zsh history and Terminal's per-session files, which can hold commands the main history file lacks.
  3. 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.
  4. 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.
  5. 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.

macOS artifacts grouped by the question they answer Six groups. What came in: Quarantine events, Safari history, Install history. What ran or was used: Unified log, KnowledgeC.db, Biome streams. What was typed: zsh sessions and history, with timestamps only when EXTENDED_HISTORY is set. Which files were touched: FSEvents, Recent items, .DS_Store, and the Spotlight store, whose index entries can outlive the file. What stays: LaunchAgents and LaunchDaemons, Login items and BTM, and the legacy login / logout hooks and StartupItems. What was allowed: TCC.db and TCC changes in the unified log, whose tccd entries can age out in minutes. Start from the time a file arrived and pivot on that time across the other groups. 1 · WHAT CAME INdownloads, installs, web visits Quarantine events Safari history Install history 2 · WHAT RAN OR WAS USEDprocesses, auth, app usage Unified log KnowledgeC.db Biome streams 3 · WHAT WAS TYPEDterminal commands per user zsh sessions & history timestamps need EXTENDED_HISTORY 4 · WHICH FILES WERE TOUCHEDcreate, open, browse, delete FSEvents Recent items (sharedfilelist) .DS_Store Spotlight store 5 · WHAT STAYSpersistence across login and boot LaunchAgents / LaunchDaemons Login items / BTM Login / logout hooks & StartupItems check ProgramArguments paths 6 · WHAT WAS ALLOWEDTCC permission grants TCC.db TCC changes in unified log tccd entries can age out fast Start where a file came in (group 1), then pivot on that time across groups 2–6. Collect live first: unified-log Info/Debug and tccd entries age out; copy Safari History.db with its -wal file.
Built from the macOS Triage rows below: each artifact placed under the investigative question it answers.
ArtifactLocation / commandWhat it provesCaveats · ATT&CK
Unified log/var/db/diagnostics/ + /var/db/uuidtext/ · log show --archive · log collectSystem-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-segbPer-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_aptPer-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 / BTMSystem Settings > Login Items · sfltool dumpbtmBackground 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.plistLegacy 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.QuarantineEventsV2Files downloaded/received with source URL and app.SQLite; entries persist after file deletion.
TCC changes in unified loglog 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_historyTerminal 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.plistTimeline 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.plistVisited 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.

  1. Preserve from the top down: cordon the node and copy the writable layer and node logs before the pod or node is recycled.
  2. 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.
  3. 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.

Container evidence by layer, and when each layer is lost Four stacked layers. Pod or container: Docker overlay diff and the mounted service account token, lost when the container is removed. Node: container runtime state and kubelet and node logs, lost to log rotation or node recycle. Control plane: K8s API audit log and managed-K8s audit logging, often off by default, and GKE does not export TokenRequest. Cloud: cloud API logs for the mapped identity, retention-limited. Attacker reach between layers: privileged pods and host-namespace escape reach the node, service account token theft reaches the API server, and IRSA or Workload Identity reaches the cloud. Below, ESXi as a separate hypervisor stack: ESXi logs, audit records and VM inventory, lost on reboot unless remote syslog is configured; ESXi boot persistence in local.sh is one of the few changes that survives the reboot. LAYEREVIDENCE (rows below)LOST WHEN PODcontainer Docker overlay diff writable layer · mounted SA token in /var/run/secrets/… container removed copy it first Privileged pods & host-namespace escapeprivileged / hostPID / hostPath → node NODEhost OS Container runtime stateKubelet / node logs crictl · /var/log/pods · journalctl -u kubelet / containerd log rotation, node recycle Service account token theftmounted or minted token → API server API SERVERcontrol plane K8s API audit logManaged-K8s audit logging exec · get secrets · create pods · serviceaccounts/token often off by default GKE: no TokenRequest IRSA / Workload Identity → the service account's cloud identity CLOUDprovider APIs Cloud API logs for the mapped identity pivot on the principal — see Cloud & Identity retention limit export early SEPARATE STACK · HYPERVISOR ESXIhost ESXi logsESXi audit recordsESXi VM inventory shell.log · auth.log · hostd.log · vmkernel.log · vmInventory.xml reboot (ramdisk) unless remote syslog ESXi boot persistence (local.sh)diff vs clean host, same build local.sh survives it Top rows vanish first: preserve pod and node evidence before the pod or node is recycled.
Built from the Containers, Kubernetes & ESXi rows below: where each artifact lives, how an attacker moves between layers, and what destroys it.
ArtifactLocation / commandWhat it provesCaveats · ATT&CK
K8s API audit logAPI server audit backend (file / webhook) · JSON linesWho (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 statecrictl ps -a · crictl inspect · /var/lib/containerd · /var/lib/docker · /var/log/pods · /var/log/containersImages, layers, mounts, container logs, start/exit times.Log rotation is aggressive; copy before node recycle.
Docker overlay diffdocker diff <ctr> · docker export · overlay2 upperdirFiles added/changed in the writable layer since image start.Lost when container is removed. Compare against image digest.
Kubelet / node logsjournalctl -u kubelet · -u containerd · host auth.logPod 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.logShell/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 recordsesxcli system auditrecords get · esxcli system auditrecords local set (ESXi 7.0 U2+) · /var/run/log/ or configured audit dirRecords 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 syslogCrowdStrike 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 docrootLists 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/rootMost 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 loggingEKS: cluster logging types incl. audit · AKS/GKE: diagnostic settings / Cloud Audit Logs — verify enabledControl-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 escapeK8s audit log: create pods with securityContext.privileged, hostPID, hostNetwork or hostPath volumes · node: nsenter -t 1 in process/audit logsPod 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 theftIn 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-tokenReads 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-forwardK8s 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 changesK8s audit log: objectRef.apiGroup=rbac.authorization.k8s.io · objectRef.resource clusterrolebindings / rolebindings · verb create / update / patch · requestObject.roleRef.name · requestObject.subjects · user.usernameWho 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 persistenceK8s audit log: verb create/patch on cronjobs (batch) or daemonsets (apps) · kubectl get cronjobs,daemonsets -A -o yamlA 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'skube-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 hostKubelet 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:

  1. Match both verbs. Older SPDY clients send an HTTP POST, logged as verb create. WebSocket clients send an HTTP GET, logged as verb get, and WebSocket is the kubectl default since 1.31. A hunt for create alone misses them.
  2. Read who, from where and what. user.username is who ran it, sourceIPs is where from, and requestURI carries the command= arguments, so it shows what was started in which pod and namespace.
  3. 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/exec subresource 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).

Who opened a shell in a pod? Pod exec, attach and port-forward in the K8s audit log Top: a user or service account runs kubectl exec, attach or port-forward. An SPDY client sends an HTTP POST, which the audit log records with verb create; a WebSocket client, the kubectl default since 1.31, sends an HTTP GET, recorded with verb get; a hunt for create alone misses WebSocket clients. Middle: one audit event holds who (user.username), from where (sourceIPs), which pod (objectRef.resource pods with subresource exec, attach or portforward, plus the pod and namespace) and what ran (requestURI with the command= arguments). Bottom: commands typed inside an interactive shell are not logged, so the event shows only the shell being opened; an exec by a human user or an unexpected service account into a production pod is hands-on-keyboard activity (T1609); the audit policy must log the pods/exec subresource at Metadata or Request level, the K8s API audit log is off by default on many self-managed clusters, and managed clusters need audit logging turned on. 1 · ONE EXEC, TWO WIRE PROTOCOLS, TWO AUDIT VERBS WHO RAN IT user or service account kubectl exec / attach / port-forward SPDY client HTTP POST WebSocket client HTTP GET · kubectl default since 1.31 audit verb create audit verb get a hunt for verb create alone misses every WebSocket client: match create and get 2 · WHAT ONE AUDIT EVENT HOLDS WHO user.username FROM WHERE sourceIPs WHICH POD objectRef.resource=pods subresource exec / attach / portforward WHAT RAN requestURI command= arguments together: who started what, in which pod and namespace, and from where 3 · WHERE THE AUDIT TRAIL STOPS commands typed inside an interactive shell are not logged the event shows the shell being opened (command= in requestURI), not what was done in it an exec by a human user or an unexpected service account into a production pod = hands-on-keyboard (T1609) no event at all unless the audit policy logs the pods/exec subresource at Metadata or Request level K8s API audit logoff by default on many self-managed clusters Managed-K8s audit loggingverify it is on before you trust an empty exec hunt
Built from the Pod exec / attach / port-forward, K8s API audit log and Managed-K8s audit logging rows above.
▸ 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:

  1. Read the binding. user.username is who made the change, requestObject.subjects is who receives the role and requestObject.roleRef.name is which role. A ClusterRoleBinding grants it across the whole cluster; a RoleBinding grants it inside one namespace.
  2. Spot the bindings that hand over the cluster. cluster-admin bound to a namespace service account, or any role bound to system: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.
  3. 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.username as system:serviceaccount:<namespace>:<name>.
Who handed out which role? Kubernetes RBAC binding changes in the audit log Top: a user or service account (user.username) creates, updates or patches a binding in the API group rbac.authorization.k8s.io. A clusterrolebinding grants a role across the whole cluster; a rolebinding grants it inside one namespace. The binding names who gets the role (requestObject.subjects: a user, group or service account) and which role (requestObject.roleRef.name, for example cluster-admin). Middle: two bindings hand over the cluster: cluster-admin bound to a namespace service account, and any role bound to system:anonymous or 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. Bottom: then follow the subject's next actions in the audit log: secret reads, privileged pods and DaemonSets; a service account appears as system:serviceaccount:namespace:name. The K8s API audit log records the RBAC changes. 1 · ONE BINDING: WHO GOT WHICH ROLE, AND WHERE WHO MADE IT user.username create / update / patch the audit verb THE BINDING · rbac.authorization.k8s.io clusterrolebindings grants the role across the whole cluster rolebindings grants it inside one namespace WHO GETS IT requestObject.subjects user, group or service account WHICH ROLE requestObject.roleRef.name for example cluster-admin one event answers: who granted which subject which role, cluster-wide or in one namespace 2 · BINDINGS THAT HAND OVER THE CLUSTER cluster-admin → a namespace service account that service account now holds the whole cluster, and so does anything that holds its token any role → system:anonymous or system:unauthenticated callers with no credentials get that role a new cluster-admin binding made by a service account or an unexpected user = privilege escalation and persistence (T1098.006) usual false positive: bootstrap, upgrade and recovery tooling re-creating bindings 3 · THEN FOLLOW THE SUBJECT SECRET READS get secrets PRIVILEGED PODS create pods · privileged / hostPath DAEMONSETS create daemonsets from the binding's timestamp on; a service account shows as system:serviceaccount:<namespace>:<name> K8s API audit logrecords every binding change RBAC role binding changesthe row this figure is built from
Built from the RBAC role binding changes, K8s API audit log, Service account token theft, Privileged pods & host-namespace escape and CronJob / DaemonSet persistence rows above.

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.

  1. Start in the sign-in log and note the linkable identifiers: User ID, Session ID, Unique Token Identifier, Resource Tenant ID.
  2. 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.
  3. 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.

Following one token across cloud logs The Entra sign-in log provides User ID, Session ID, Unique Token Identifier and Resource Tenant ID. Searching the M365 Unified Audit Log and resource-side audit logs for the same values ties the token to its actions. Graph activity logs corroborate token use by AppId, UserId and IP, but must be enabled beforehand. A missing sign-in record is not proof of no access. STARTTHEN SEARCH ENTRA SIGN-IN LOG User IDSession IDUnique Token IdentifierResource Tenant ID linkable identifiers same values M365 Unified Audit LogExchange · SharePoint · OneDrive · Teams Resource-side audit logsworkload logs for the same session/token corroborate Microsoft Graph activity logsAppId · UserId · IP · RequestUri — must be enabled PIVOTsession / token ID, not just the UPN (AiTM replay keeps the session ID across IPs) GAPno sign-in record is not proof of no access: corroborate via Graph logs, UAL and workload logs Graph activity logs have no history before they were enabled.
Built from the Entra linkable identifiers, Graph activity logs, M365 UAL and sign-in log gaps rows below.
▸ 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.

  1. Find the grant: search the Entra audit log / UAL for the activity Consent to application (Entra illicit consent grant). Note who consented, which app, and ConsentAction.Permissions. IsAdminConsent or OnBehalfOfAll = True means tenant-wide access.
  2. Judge the scopes: offline_access gives the app a refresh token; together with Mail.ReadWrite, Files.ReadWrite.All or Chat.Read it fits mailbox and file theft.
  3. Follow what the app did: search Microsoft Graph activity logs by the app's AppId, if they were enabled before the incident.
  4. 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.

OAuth consent grant flow, and access a password reset does not remove Top: a user clicks Accept on an app's consent prompt; Entra ID records Consent to application, where IsAdminConsent or OnBehalfOfAll = True means tenant-wide access; the app's service principal gets a refresh token through offline_access; the app then calls Microsoft Graph for mail, files and chat within the granted scopes. Evidence: the Entra illicit consent grant row and Microsoft Graph activity logs. Bottom: four kinds of access that survive a password reset. The consent grant keeps the app's refresh token until the grant and service principal are revoked. A GCP user-managed service account key works from anywhere until it is deleted or disabled. A client secret or certificate added to an app registration or service principal lets the attacker sign in as the app until the added key is removed. A rogue external MFA provider must be removed before passwords are reset. 1 · HOW THE GRANT HAPPENS USER clicks Accept on an app's consent prompt no password is stolen ENTRA ID Consent to application IsAdminConsent or OnBehalfOfAll = True means tenant-wide APP service principal gets a refresh token (offline_access) MICROSOFT GRAPH mail · files · chat within the granted scopes calls carry the app's AppId evidence Entra illicit consent grant Microsoft Graph activity logs 2 · WHAT A PASSWORD RESET DOES NOT REMOVE each of these keeps working until it is removed on its own Entra illicit consent grant survives: the app's refresh token remove: revoke the grant and the service principal GCP service account key creation survives: the user-managed key, usable from anywhere remove: delete or disable the key Entra app / service principal credential added survives: the added client secret or certificate remove: the added key, then review app permissions Entra Authentication Methods Policy audit survives: a rogue external MFA provider remove it before resetting passwords attacker already held an admin role UAL entry for a consent can take 30 min to 24 h to appear. Graph activity logs have no history before they were enabled.
Built from the Entra illicit consent grant, Microsoft Graph activity logs, GCP service account key creation, Entra app / service principal credential added and Entra Authentication Methods Policy audit rows below.
SourceWhere / howWhat it provesCaveats · ATT&CK
Entra ID sign-in logsEntra admin center > Monitoring > Sign-in logs · Graph API · diagnostic export to Log Analytics / storageInteractive, 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 logsEntra admin center > Audit logs · Graph auditLogs/directoryAuditsDirectory 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 LogPurview 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 CloudTrailEvent 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 LogsCloud Logging: cloudaudit.googleapis.com/activity (Admin Activity) · /data_access · /system_eventAdmin 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 identifiersSign-in log fields: User ID · Session ID · Unique Token Identifier · Resource Tenant ID → search workload audit logs for the same valuesTies 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 eventsCloudTrail: 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 membershipCloudTrail: 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 logsEntra 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 auditAudit logs · activities “Authentication Methods Policy Update” / “Reset” · modifiedPropertiesTenant-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 logsCloudWatch log group /aws/eks/<cluster>/cluster (api, audit, authenticator, controllerManager, scheduler) · CloudTrail for the AWS sideKubernetes 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 gapsSign-in logs vs Graph activity logs vs M365 UAL vs resource-side audit logsResearchers (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 IPProofpoint (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 eventsTrail event selector: NetworkActivity (VPC endpoint events) · off by defaultAPI 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 LogsPer 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-statusNetwork 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 logsBucket property Server access logging → target bucket (same Region and account; space-delimited text, - for unknown fields) or CloudWatch Logs (JSON) · off by defaultOne 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-insSign-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 exfiltrationMicrosoftGraphActivityLogs: RequestUri on drives/items /content · app/token IDs · bulk-request volume per short windowHigh-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 downloadsM365 UAL: FileSyncDownloadedFull · UserAgent · ClientIP · device IDTechnique 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 recordsProgrammatic 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 integrityS3 digest files (CloudTrail-Digest/) · aws cloudtrail validate-logs · trail setting Enable log file validationSHA-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 eventsCloudTrail: 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 tablesAdvanced 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 creationAdmin 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 grantEntra audit log / UAL: activity Consent to application · fields ConsentContext.IsAdminConsent, ConsentContext.OnBehalfOfAll, ConsentAction.Permissions, ConsentAction.ReasonWhich 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 phishingSign-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 addedEntra 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 persistenceCloudTrail (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 LogAzure portal > Monitor > Activity log · Log Analytics table AzureActivity (via a subscription diagnostic setting) · az monitor activity-log listSubscription 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 CommandActivity 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.logA 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 impersonationData Access audit log, iamcredentials.googleapis.com: protoPayload.methodName="GenerateAccessToken" · GenerateIdToken · SignBlob · SignJwt · in later calls: protoPayload.authenticationInfo.serviceAccountDelegationInfo[].firstPartyPrincipal.principalEmailA 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 tamperingAdmin Activity audit log, logging.googleapis.com: protoPayload.methodName="google.logging.v2.ConfigServiceV2.DeleteSink" · UpdateSink · DeleteBucket · gcloud logging sinks listSomeone 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 accessCloudTrail (eventSource secretsmanager.amazonaws.com): GetSecretValue (one entry per secret, also for each secret in a BatchGetSecretValue call) · ListSecrets · DescribeSecret · PutResourcePolicy · DeleteSecretWhich 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 sharingCloudTrail ec2.amazonaws.com: ModifySnapshotAttribute with createVolumePermission add (user ID = recipient account; group all = public) · ModifyImageAttribute launchPermission for AMIs · owner-side SharedSnapshotCopyInitiated / SharedSnapshotVolumeCreatedA 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 changesCloudTrail (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 changesEntra 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 > modifiedPropertiesWho 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 changesEntra 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-MgDomainFederationConfigurationWho 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 changesCloudTrail management events (eventSource s3.amazonaws.com, 90 days in event history): PutBucketPolicy · PutBucketAcl · PutBucketPublicAccessBlock (bucket) · PutAccountPublicAccessBlock (account) · bucket in requestParameters.bucketNameWho 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 delegationUAL 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-MailboxWho 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
First 30 minutes in cloud IR: export sign-in/audit/CloudTrail logs before retention lapses; list new credentials, app consents, inbox rules and trail changes created by the suspect identity; revoke sessions/tokens, not just passwords.
▸ 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:

  1. Find the call in the control plane: in the Azure Activity Log, look for MICROSOFT.COMPUTE/VIRTUALMACHINES/RUNCOMMAND/ACTION. Note the caller (UPN or SPN), caller IP, time and correlationId.
  2. 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.
  3. 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.

Azure VM Run Command: from the control plane to files on the VM Top, how a run happens: a caller (user or service principal) asks Azure Resource Manager to run a command; the control plane records it in the Azure Activity Log; the VM agent downloads the script into the plugin folder, with no RDP or SSH logon; the script runs on the VM, as SYSTEM on Windows. Bottom left, the control-plane evidence: Azure Activity Log operation MICROSOFT.COMPUTE/VIRTUALMACHINES/RUNCOMMAND/ACTION with caller, caller IP, correlationId and category Administrative; it does not hold the script text and is kept 90 days in Azure. Bottom right, the evidence on the VM: on Windows the C:\Packages\Plugins\Microsoft.CPlat.Core.RunCommandWindows version folder with Downloads\script*.ps1 and Status, and RunCommandExtension.log under C:\WindowsAzure\Logs\Plugins; on Linux /var/lib/waagent/run-command/download with script.sh, stdout and stderr, and /var/log/azure/run-command/handler.log. Collect the plugin folder from the VM early, and pivot on the caller in the Entra sign-in logs. 1 · HOW A RUN HAPPENS CALLER UPN or SPN user or service principal asks to run a script RESOURCE MANAGER Run Command action Azure control plane writes the Activity Log VM AGENT downloads the script into the plugin folder no RDP / SSH logon VM script runs Windows: as SYSTEM powershell.exe … -File records the call leaves files 2 · WHERE THE EVIDENCE IS IN AZURE (control plane) Azure Activity Log operationName MICROSOFT.COMPUTE/VIRTUALMACHINES/ RUNCOMMAND/ACTION caller (UPN or SPN) · caller IP correlationId groups one operation category Administrative · claims does not hold the script text kept 90 days in Azure, then deleted export to a workspace or storage ON THE VM (collect early) Azure VM Run Command Windows C:\Packages\Plugins\ Microsoft.CPlat.Core.RunCommandWindows\<ver>\ Downloads\script*.ps1 · Status C:\WindowsAzure\Logs\Plugins\… …\RunCommandExtension.log Linux /var/lib/waagent/run-command/download/<n>/ script.sh · stdout · stderr /var/log/azure/run-command/handler.log Only the VM holds the script and its result: collect the plugin folder from the VM early. Pivot on the caller: the Activity Log is separate from the Entra logs, so check its sign-ins there. Version folder names vary: collect the whole plugin folder, not one path.
Built from the Azure Activity Log and Azure VM Run Command rows above.
▸ 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:

  1. 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, and bindingDeltas.action="ADD" lists every new grant.
  2. Impersonate: the principal mints a short-lived token or signature as the service account. GCP service account impersonation covers these iamcredentials.googleapis.com calls, which are in the Data Access log only if it was enabled for the IAM API.
  3. Act: calls made with the token show the service account as the principal. Read serviceAccountDelegationInfo for 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.

GCP service account impersonation: grant, impersonate, act Top, a typical path in three steps: a role that lets a principal act as a service account is granted (SetIamPolicy); the principal mints a short-lived token or signature as the service account (GenerateAccessToken); later API calls show the service account as the principal, with the real caller in the delegation info. Middle, where each step is logged. Grant: the Admin Activity audit log, always on, method SetIamPolicy at project, folder or org level or google.iam.admin.v1.SetIAMPolicy on a service account, with policyDelta.bindingDeltas action ADD or REMOVE, role and member, and the caller in authenticationInfo.principalEmail; red flags are owner or editor, iam.serviceAccountTokenCreator, iam.serviceAccountUser, external accounts and allUsers. Impersonate: the Data Access audit log for iamcredentials.googleapis.com, methods GenerateAccessToken, GenerateIdToken, SignBlob and SignJwt, not logged unless Data Access logs are enabled for the IAM API. Act: the principal shown is the service account; the real caller is in authenticationInfo.serviceAccountDelegationInfo[].firstPartyPrincipal.principalEmail; not every service records delegation info. Bottom, the long-lived alternative: a user-managed key created with CreateServiceAccountKey in the Admin Activity log; later calls carry authenticationInfo.serviceAccountKeyName; the key works from anywhere until it is deleted or disabled. Admin Activity logs are always on; Data Access logs are off by default for most services, BigQuery excepted. 1 · A TYPICAL PATH GRANT SetIamPolicy a role binding is added e.g. serviceAccountTokenCreator IMPERSONATE GenerateAccessToken a short-lived token or signature minted as the service account ACT AS THE SERVICE ACCOUNT later API calls principal = the service account real caller in delegation info 2 · WHERE EACH STEP IS LOGGED ADMIN ACTIVITY LOG · always on GCP IAM policy change (role grant) methodName SetIamPolicy (project/folder/org) google.iam.admin.v1.SetIAMPolicy (on a service account) policyDelta.bindingDeltas action ADD / REMOVE · role · member caller: authenticationInfo .principalEmail red flags in the grant owner / editor iam.serviceAccountTokenCreator iam.serviceAccountUser · allUsers DATA ACCESS LOG · must be enabled GCP service account impersonation iamcredentials.googleapis.com GenerateAccessToken GenerateIdToken SignBlob · SignJwt who minted a token or signature as which service account not logged unless Data Access logs are on for the IAM API enable before an incident LATER CALLS · the service called principal shown: the service account real caller (pivot here) authenticationInfo .serviceAccountDelegationInfo[] .firstPartyPrincipal .principalEmail pivot there, not at the service account not every service records delegation info 3 · THE LONG-LIVED ALTERNATIVE TO STEP 2 GCP service account key creation CreateServiceAccountKey (Admin Activity) · later calls carry serviceAccountKeyName a user-managed key works from anywhere until it is deleted or disabled: a credential reset does not revoke it GCP Cloud Audit LogsAdmin Activity is always on; Data Access is off by default for most services (BigQuery excepted).
Built from the GCP IAM policy change (role grant), GCP service account impersonation, GCP service account key creation and GCP Cloud Audit Logs rows above.
▸ 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:

  1. Which interface: account-id and interface-id tie the record to an ENI, and so to the instance behind it.
  2. 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.
  3. How much and when: packets and bytes between start and end, which is how exfiltration volume shows up.
  4. Allowed or not: action (ACCEPT / REJECT) and log-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.

AWS VPC Flow Logs: what a record shows and what it never shows Top: the 14 fields of a default (v2) flow log record, grouped as which interface (version, account-id, interface-id), the 5-tuple (srcaddr, dstaddr, srcport, dstport, protocol), how much (packets, bytes), when (start, end) and the verdict (action ACCEPT or REJECT, log-status); one record per 5-tuple per network interface per 1- or 10-minute window, with no payload and no DNS names. Middle: traffic between a network interface and other hosts in the VPC (lateral movement) or outside addresses (C2, exfiltration volume) is logged; traffic to the Amazon DNS resolver, instance metadata at 169.254.169.254, Amazon Time Sync, DHCP and ARP is never logged; for DNS use Route 53 Resolver query logs. Bottom: flow logs are off by default and not retroactive, records can be skipped under capacity limits, and CloudTrail records DeleteFlowLogs when a flow log is removed. 1 · WHAT ONE RECORD HOLDS (default v2 format, 14 fields) WHICH INTERFACE version · account-id interface-id WHO TALKED TO WHOM · 5-tuple srcaddr · dstaddr srcport · dstport · protocol HOW MUCH packets · bytes WHEN start · end 1 or 10 min VERDICT action · log-status ACCEPT / REJECT one record per 5-tuple, per network interface, per aggregation window no payload · no DNS names 2 · WHAT IT SEES, AND WHAT IT NEVER SEES Other hosts in the VPC lateral movement logged NETWORK INTERFACE (ENI) instance in the VPC flow log on its VPC, subnet or ENI Outside addresses C2 · exfiltration volume logged NEVER LOGGED · no record even with flow logs on Amazon DNS resolver use Resolver query logs Instance metadata 169.254.169.254 Amazon Time Sync DHCP ARP a quiet flow log says nothing about DNS lookups or instance-metadata reads: look elsewhere for those 3 · BEFORE YOU TRUST AN EMPTY RESULT off by default and not retroactive: no records from before it was turned on records can be skipped under capacity limits: a missing record is not proof of no traffic AWS CloudTrailDeleteFlowLogs records who removed a flow log (attacker removal) AWS VPC Flow Logsdelivered to CloudWatch Logs, S3 or Firehose: export what you need early
Built from the AWS VPC Flow Logs and AWS CloudTrail rows above.
▸ 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:

  1. 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.
  2. 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.
  3. 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.

Who read the bucket? Where one S3 request is recorded Top: a requester (an IAM principal, or anonymous) sends a request to an S3 bucket. The request can be recorded in three places. AWS CloudTrail management events hold bucket configuration calls such as PutBucketLogging, kept 90 days in event history, but not object reads. CloudTrail S3 data events hold object-level calls such as GetObject with userIdentity.arn and sourceIPAddress, and are opt-in. AWS S3 server access logs hold one record per request to the bucket, including anonymous and AccessDenied requests, are off by default and are delivered best-effort. Middle: a server access log record holds the requester canonical user ID and authentication type, the remote IP, the operation such as REST.GET.OBJECT and the key, and the HTTP status and bytes sent; it is delivered to a target bucket in the same Region and account as space-delimited text, or to CloudWatch Logs as JSON. Bottom: logging is off by default and not retroactive, delivery is best-effort so absence is not proof, CloudTrail PutBucketLogging records who changed or removed bucket logging, and CloudTrail S3 data events can be paired with server access logs where both are enabled. 1 · ONE REQUEST TO A BUCKET, THREE PLACES IT CAN BE RECORDED REQUESTER IAM principal, or anonymous request S3 BUCKET objects, addressed by key reading an object is not a management event: no record of it unless data events or server access logging was already on AWS CloudTrail management events, e.g. PutBucketLogging event history keeps 90 days object reads (GET) are not here CloudTrail S3 data events object-level calls, e.g. GetObject userIdentity.arn · sourceIPAddress opt-in: must be enabled beforehand AWS S3 server access logs one record per request to the bucket includes anonymous requests and AccessDenied (failed authorization) off by default · best-effort delivery 2 · WHAT ONE SERVER ACCESS LOG RECORD HOLDS WHO requester (canonical user ID) authentication type FROM WHERE remote IP DID WHAT operation · key e.g. REST.GET.OBJECT RESULT HTTP status · bytes sent delivered to a target bucket (same Region and account; space-delimited text, "-" = unknown) or to CloudWatch Logs (JSON) AccessDenied or anonymous runs point to bucket enumeration; successful GETs with bytes sent point to object download 3 · BEFORE YOU TRUST AN EMPTY RESULT off by default and not retroactive: no records from before logging was turned on best-effort delivery: records can arrive late or be missing, so absence is not proof AWS CloudTrailPutBucketLogging records who changed or removed bucket logging (attacker removal) CloudTrail S3 data eventspair them with server access logs where both are enabled
Built from the AWS S3 server access logs and AWS CloudTrail rows above.
▸ 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:

  1. Make the copy: CreateSnapshot or CopySnapshot. A copy can carry a different KMS key, so note which key the shared snapshot uses and which call produced it.
  2. Share it: ModifySnapshotAttribute with a createVolumePermission add. A user ID is the recipient account; group all makes the snapshot public. For an AMI the call is ModifyImageAttribute with launchPermission.
  3. 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 / SharedSnapshotVolumeCreated events.

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.

EBS snapshot and AMI sharing: how a whole disk leaves the account Top, three steps: a snapshot is made with CreateSnapshot or CopySnapshot, and a copy can carry a different KMS key; it is shared with ModifySnapshotAttribute and a createVolumePermission add, where a user ID is the recipient account and group all makes it public, or for an AMI with ModifyImageAttribute and launchPermission; the recipient account copies it or creates a volume and reads the whole disk in its own account, outside your logs. Below, the evidence: your CloudTrail (ec2.amazonaws.com) holds the create, copy and share calls, to pivot on userIdentity.arn, source IP and the snapshot ID; the owner-side SharedSnapshotCopyInitiated and SharedSnapshotVolumeCreated events show the recipient using it. Middle, what the key allows: an unencrypted snapshot can be shared with any account or made public; one encrypted with the default KMS key cannot be shared with other accounts or made public; an encrypted snapshot can never be made public. Bottom, reading the share event: an add for an account ID outside your organization, or group all, is exfiltration of a whole volume (T1537); same-organization sharing is the usual false positive. 1 · HOW A WHOLE DISK LEAVES THE ACCOUNT COPY CreateSnapshot or CopySnapshot a copy can carry a different KMS key: note which one SHARE ModifySnapshotAttribute createVolumePermission add user ID = recipient account group all = public RECIPIENT ACCOUNT copy or create a volume reads the whole disk in its own account, outside your logs AMI: ModifyImageAttribute with launchPermission IN YOUR TRAIL · ec2.amazonaws.com AWS CloudTrail CreateSnapshot / CopySnapshot → ModifySnapshotAttribute pivot on userIdentity.arn · source IP · snapshot ID trace the shared snapshot back to the call that made it OWNER-SIDE EVENTS · your trail SharedSnapshotCopyInitiated SharedSnapshotVolumeCreated the recipient used the share 2 · CAN THIS SNAPSHOT LEAVE? THE KEY DECIDES NOT ENCRYPTED can be shared with any account or made public (group all) ENCRYPTED · DEFAULT KMS KEY cannot be shared with other accounts cannot be made public look for a CopySnapshot to another key ANY ENCRYPTED SNAPSHOT can never be made public check the snapshot's key and any CopySnapshot that produced it 3 · READING THE SHARE EVENT an add for an account ID outside your organization, or group all = exfiltration of a whole volume (T1537) same-organization sharing is the usual false positive: compare the recipient with your own account list no snapshot share in the trail? check ModifyImageAttribute with launchPermission for AMIs too
Built from the AWS EBS snapshot / AMI sharing and AWS CloudTrail rows above.
▸ 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.

  1. The permissions policy (what it can do). Three ways to add rights: attach a managed policy (AttachUserPolicy / AttachRolePolicy / AttachGroupPolicy; requestParameters.policyArn names it), write an inline policy (PutUserPolicy / PutRolePolicy), or add a new version of a managed policy with CreatePolicyVersion and setAsDefault, which changes it for every identity it is attached to.
  2. The trust policy (who may become this role). UpdateAssumeRolePolicy replaces it, and requestParameters.policyDocument holds the new version. An outside account ID or a broad principal there 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 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.

Two ways to widen an AWS identity's reach: the permissions policy and the role trust policy Top: the permissions policy decides what an identity can do. Rights are added by attaching a managed policy (AttachUserPolicy, AttachRolePolicy, AttachGroupPolicy, with requestParameters.policyArn), by writing an inline policy (PutUserPolicy, PutRolePolicy), or by CreatePolicyVersion with setAsDefault, which changes a managed policy for everyone it is attached to; AdministratorAccess or a *:* inline policy added by a non-admin pipeline identity is privilege escalation. Middle: the trust policy decides who may become a role. An identity in your account calls UpdateAssumeRolePolicy and the trust policy now names an outside account, which can then call AssumeRole into the role; requestParameters.policyDocument holds the new trust policy, to compare with the old one and restore, and later use shows as AssumeRole events against the role ARN. Bottom: all of these calls come from iam.amazonaws.com and are recorded in us-east-1; an outside account or broad principal in a trust policy is persistence (T1098.003); IaC tooling such as Terraform and CloudFormation is the usual false positive; AWS CloudTrail records the calls and AWS IAM credential persistence covers new access keys and console passwords. 1 · WHAT CAN THIS IDENTITY DO? THE PERMISSIONS POLICY ATTACH A MANAGED POLICY AttachUserPolicy AttachRolePolicy · AttachGroupPolicy which one: requestParameters.policyArn WRITE AN INLINE POLICY PutUserPolicy · PutRolePolicy the policy lives on the identity REWRITE A MANAGED POLICY CreatePolicyVersion with setAsDefault applies to everyone it is attached to AdministratorAccess (or *:* inline) added by a non-admin pipeline identity = privilege escalation 2 · WHO MAY BECOME THIS ROLE? THE TRUST POLICY WHO CHANGED IT an identity in your account UpdateAssumeRolePolicy rewrites the trust ROLE trust policy now names an outside account AssumeRole from outside OUTSIDE ACCOUNT not one of yours requestParameters.policyDocument holds the new trust policy: compare it with the old one and restore it later use shows as AssumeRole events against the role ARN 3 · READING THE EVENTS eventSource iam.amazonaws.com, recorded in us-east-1: search there whatever Region the workload is in an outside account ID or a broad principal in a trust policy = persistence (T1098.003) IaC tooling (Terraform, CloudFormation user agents) is the usual false positive: check who called and how AWS CloudTrailrecords every call in this figure AWS IAM policy & role trust changesthe row this figure is built from AWS IAM credential persistencenew access keys and console passwords: the other way to keep access
Built from the AWS IAM policy & role trust changes, AWS IAM credential persistence and AWS CloudTrail rows above.
▸ 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:

  1. Find the change before it ages out. The activities are Add conditional access policy, Update conditional access policy and Delete conditional access policy; in Log Analytics they are in AuditLogs under OperationName. The audit log is kept 30 days by default unless it is exported through a diagnostic setting.
  2. Diff old and new. TargetResources > modifiedProperties holds 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).
  3. 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.
What a Conditional Access change can quietly switch off, and where it is recorded Top: an admin account or app adds, updates or deletes a Conditional Access policy. The Entra audit log records it with Service Conditional Access and Category Policy, as the activities Add, Update or Delete conditional access policy; with a diagnostic setting it also reaches Log Analytics in the AuditLogs table under OperationName. The audit log is kept 30 days by default, so export it before it ages out. Middle: TargetResources then modifiedProperties holds the policy JSON before and after the change. Four changes to look for: 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. Any of these is persistence and defense evasion (T1556.009), and a delete removes every control in that policy. Bottom: take the time and actor of the change, check the actor's sign-ins in the Entra ID sign-in logs, then the sign-ins the weakened policy now lets through, from the new location or by the excluded accounts. 1 · WHERE A CONDITIONAL ACCESS CHANGE IS RECORDED WHO CHANGED IT an admin account or app ENTRA AUDIT LOG Service: Conditional Access Category: Policy Add / Update / Delete conditional access policy LOG ANALYTICS AuditLogs OperationName only with a diagnostic setting kept 30 days by default: export it through a diagnostic setting before it ages out 2 · DIFF THE POLICY: OLD VALUE vs NEW VALUE TargetResources > modifiedProperties holds the policy JSON before and after the change NEW TRUSTED LOCATION new trusted IP range or named location MFA GRANT REMOVED the policy no longer requires MFA NEW EXCLUSION a user or group is left out of the policy REPORT-ONLY OR OFF the policy is still there but no longer enforces any of these = persistence and defense evasion (T1556.009) a Delete conditional access policy event removes every control in that policy at once 3 · THEN CHECK WHAT THE CHANGE LET THROUGH THE CHANGE time + who changed it THE ACTOR'S SIGN-INS around the change WHAT IT LETS THROUGH new location, excluded accounts Entra ID audit logswhere every Conditional Access edit is recorded Entra ID sign-in logscorrelate the actor's sign-ins Entra Conditional Access policy changesthe row this figure is built from
Built from the Entra Conditional Access policy changes, Entra ID audit logs and Entra ID sign-in logs rows above.
▸ 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:

  1. Find the change. In the Entra ID audit logs (Category DirectoryManagement, also in the UAL and in Log Analytics AuditLogs under OperationName), Set domain authentication converts a domain to federated and Set federation settings on domain changes the trust of a domain that is already federated. For a new domain, Add unverified domain and Verify domain come first.
  2. Pull the configuration the event leaves out. The event names the domain but not the new settings. Run Get-MgDomainFederationConfiguration and compare the issuer URI and the signing certificates with your real IdP.
  3. 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.
One federation change opens every account in the domain Top: the Entra audit log, Category DirectoryManagement, also in the Unified Audit Log and in Log Analytics AuditLogs under OperationName, records the chain. For a new domain the precursors Add unverified domain and Verify domain come first; Set domain authentication converts a domain to federated; Set federation settings on domain changes the trust settings of a domain that is already federated. Middle: the audit event names the domain but not the new configuration, so run Get-MgDomainFederationConfiguration and compare the issuer URI and signing certificates with the real identity provider. Bottom: why it matters: an identity provider the attacker controls, or a signing certificate they added, issues SAML tokens that the tenant accepts for any user in that domain, without any user's password. This is the trust change APT29 made in the SolarWinds campaign. Red flags: an unexpected federation change, a new secondary signing certificate, or a newly verified domain set to federated is a tenant-wide backdoor (T1484.002). 1 · THE CHAIN IN THE ENTRA AUDIT LOG · Category DirectoryManagement PRECURSOR · new domain Add unverified domain PRECURSOR Verify domain MANAGED → FEDERATED Set domain authentication ALREADY FEDERATED Set federation settings on domain also in the UAL, and in Log Analytics AuditLogs under OperationName · an existing federated domain shows only the last event 2 · THE EVENT NAMES THE DOMAIN, NOT THE NEW TRUST THE AUDIT EVENT which domain, who, when no issuer, no certificate PULL THE CURRENT SETTINGS Get-MgDomainFederationConfiguration COMPARE WITH YOUR REAL IdP issuer URI signing certificates 3 · WHY ONE CHANGE OPENS EVERY ACCOUNT IN THE DOMAIN ATTACKER'S TRUST their own IdP, or an added signing certificate SIGNS A SAML TOKEN for any user in that domain no user's password needed THE TENANT ACCEPTS IT signs in as that user a password reset does not help the trust change APT29 made in the SolarWinds campaign red flags: an unexpected federation change · a new secondary signing certificate · a newly verified domain set to federated = tenant-wide backdoor (T1484.002) Entra ID audit logswhere the domain and federation events are recorded Entra domain federation changesthe row this figure is built from
Built from the Entra domain federation changes and Entra ID audit logs rows above.
▸ 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:

  1. Find the changes. In AWS CloudTrail (eventSource s3.amazonaws.com, 90 days in event history) look for PutBucketPolicy and PutBucketAcl, and for the Block Public Access calls that often come first: PutBucketPublicAccessBlock for one bucket, PutAccountPublicAccessBlock for the whole account. The bucket is in requestParameters.bucketName.
  2. Read the grant. The policy JSON or the ACL grant in requestParameters names who was let in. A Block Public Access call that sets BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy or RestrictPublicBuckets to 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.
  3. Follow the actor, then the reads. Pivot on userIdentity.arn and 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.
Who opened the bucket, and to whom: S3 policy, ACL and Block Public Access changes Top: CloudTrail management events with eventSource s3.amazonaws.com, kept 90 days in event history, record how a bucket is opened. Often first, the guard rail is removed: PutBucketPublicAccessBlock for one bucket or PutAccountPublicAccessBlock for the account, with a flag set to false. Then the bucket is opened: PutBucketPolicy or PutBucketAcl, with the principals in requestParameters. The reads that follow are data events, absent unless a trail already collected them; S3 server access logs may hold them if logging was on. The bucket name is in requestParameters.bucketName. Middle: the four Block Public Access flags are BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy and RestrictPublicBuckets; any one set to false removes a guard rail. Bottom: pivot on userIdentity.arn and source IP, and read the grant. Red flags: a policy Allow for an account ID outside your organization, a "Principal": "*" grant, an AllUsers or AuthenticatedUsers ACL grant, or Block Public Access weakened shortly before a policy change; any of these means the bucket was backdoored for exfiltration (T1537, T1530; T1686.001 for weakened Block Public Access, per SigmaHQ). Partner and vendor cross-account access is the usual false positive. 1 · HOW A BUCKET IS OPENED · CloudTrail management events, s3.amazonaws.com OFTEN FIRST · GUARD RAIL OFF PutBucketPublicAccessBlockbucket PutAccountPublicAccessBlockaccount a flag set to false THEN THE BUCKET IS OPENED PutBucketPolicy PutBucketAcl principals in requestParameters THE READS THAT FOLLOW data events, not in this log absent unless a trail was already collecting them 90 days in event history · bucket in requestParameters.bucketName · reads: S3 server access logs, if logging was on 2 · THE FOUR BLOCK PUBLIC ACCESS FLAGS · any one set to false removes a guard rail BlockPublicAcls IgnorePublicAcls BlockPublicPolicy RestrictPublicBuckets 3 · READ THE GRANT · who opened the bucket, and to whom PIVOT ON THE ACTOR userIdentity.arn and source IP policy Allow for an account ID outside your organization "Principal": "*" grant AllUsers / AuthenticatedUsers ACL grant Block Public Access weakened shortly before a policy change any of these = bucket backdoored for exfiltration (T1537 · T1530; guard rail weakened: T1686.001, per SigmaHQ) usual false positive: partner and vendor cross-account access AWS CloudTrailwhere the configuration events are recorded, 90 days in event history AWS S3 server access logsthe reads that followed, if logging was on AWS S3 bucket policy, ACL & Block Public Access changesthe row this figure is built from
Built from the AWS S3 bucket policy, ACL & Block Public Access changes, AWS CloudTrail and AWS S3 server access logs rows above.
▸ 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:

  1. Find the change. In the Admin Activity audit log (GCP Cloud Audit Logs, logging.googleapis.com) look for protoPayload.methodName google.logging.v2.ConfigServiceV2.DeleteSink, UpdateSink or DeleteBucket. Admin Activity entries go to _Required, so the tampering call itself stays on record.
  2. Work out what was lost. A deleted or rewritten export sink stops logs reaching the SIEM, BigQuery or Storage export. Exclusion filters on the _Default sink 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.
  3. Close the gap. Sink changes are not retroactive: logs routed while the sink was gone or filtered are never exported later, so read _Required for that window. List the sinks as they are now with gcloud logging sinks list. Infrastructure-as-code service accounts are the usual false positive.
What a GCP log sink change cuts off, and where the record survives Top: the Log Router sends every log entry on through sinks. The _Required sink cannot be changed, excluded or deleted; it feeds the _Required bucket, which holds the Admin Activity and System Event audit logs and keeps them 400 days. On the _Default sink, exclusion filters override inclusion filters, so an added or edited exclusion silently drops logs before they reach the _Default bucket. Export sinks to a SIEM, BigQuery or Cloud Storage can be deleted or rewritten, and the export then stops. Middle: the changes are Admin Activity audit log entries from logging.googleapis.com with protoPayload.methodName google.logging.v2.ConfigServiceV2.DeleteSink (export sink removed), UpdateSink (sink rewritten) or DeleteBucket (bucket deleted; it stays pending for 7 days and can be restored). Because Admin Activity entries go to _Required, the change itself stays on record. Bottom: list current sinks with gcloud logging sinks list; sink changes are not retroactive, so logs routed while the sink was gone or filtered are never exported later and _Required must be read for the gap; infrastructure-as-code service accounts are the usual false positive. ATT&CK T1685.002. 1 · WHERE THE LOG ROUTER SENDS EACH ENTRY · and what each sink can lose LOG ROUTER every log entry goes on through the sinks _Required SINK cannot be changed, excluded or deleted _Required BUCKET Admin Activity · System Event audit logs kept 400 days _Default SINK exclusions override its inclusions an added exclusion silently drops logs _Default BUCKET what the _Default sink routes here excluded logs never arrive EXPORT SINKS deleted or rewritten: the export stops SIEM · BigQuery · Cloud Storage logs stop arriving, and are not sent later (not retroactive) 2 · WHAT THE CHANGE LOOKS LIKE · Admin Activity audit log, logging.googleapis.com protoPayload.methodName =google.logging.v2.ConfigServiceV2. … DeleteSink an export sink removed UpdateSink a sink rewritten DeleteBucket pending 7 days, can be restored these entries go to _Required, which nobody can change: the tampering itself stays on record 3 · CLOSE THE GAP WHAT ROUTES NOW gcloud logging sinks list compare with the sinks you expect READ _Required FOR THE GAP logs routed while the sink was gone or filtered are never exported later USUAL FALSE POSITIVE infrastructure-as-code service accounts · ATT&CK T1685.002 GCP log sink & log bucket tamperingthe row this figure is built from GCP Cloud Audit LogsAdmin Activity is always on and holds the sink and bucket changes
Built from the GCP log sink & log bucket tampering and GCP Cloud Audit Logs rows above.
▸ 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:

  1. 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 to Default or Anonymous on the root or Inbox let other accounts keep reading those folders. Check all four.
  2. Read the ExchangeAdmin record. In the Unified Audit Log the target mailbox is in Parameters.Identity, the grantee in User / Trustee, the right in AccessRights and the caller in UserId. A FullAccess grant made with -AutoMapping $false does not appear in the grantee's Outlook: a quiet grant.
  3. 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-RecipientPermission and remove them. The M365 Unified Audit Log row covers what else lands in the same log.
Who else can read or send as a mailbox, and why a password reset does not stop them Top: four separate Exchange Online grants. FullAccess, granted with Add-MailboxPermission, reads the whole mailbox and is auto-mapped into the grantee's Outlook unless -AutoMapping $false was passed, which makes it a quiet grant. Send As is granted with Add-RecipientPermission. Send on Behalf is granted with Set-Mailbox GrantSendOnBehalfTo. Folder permissions, granted with Add-MailboxFolderPermission or Set-MailboxFolderPermission to the Default or Anonymous user on the mailbox root or Inbox, let other accounts keep reading those folders. FullAccess does not include Send As or Send on Behalf. Middle: each grant is an ExchangeAdmin record in the Unified Audit Log: Parameters.Identity is the target mailbox, User or Trustee the grantee, AccessRights the right and UserId the caller; search with Search-UnifiedAuditLog -RecordType ExchangeAdmin and the three operations. Bottom: delegation is tied to the grantee, so it survives the victim's password reset and session revocation; list grants with Get-MailboxPermission and Get-RecipientPermission and remove them. ATT&CK T1098.002. 1 · FOUR GRANTS, FOUR SEPARATE RIGHTS FULL ACCESS Add-MailboxPermission reads the whole mailbox auto-mapped in Outlook unless-AutoMapping $false $false = a quiet grant SEND AS Add-RecipientPermission sends mail as the mailbox SEND ON BEHALF Set-Mailbox GrantSendOnBehalfTo sends on behalf of it FOLDER PERMISSION Add-MailboxFolderPermission Set-MailboxFolderPermission to Default or Anonymous on the root or Inbox other accounts keep reading FullAccess does not include Send As or Send on Behalf: each is a separate grant, so check all four 2 · READ THE ExchangeAdmin RECORD IN THE UNIFIED AUDIT LOG Parameters.Identity → the target mailbox User / Trustee → the grantee AccessRights → the right given UserId → who made the change search:Search-UnifiedAuditLog -RecordType ExchangeAdmin -Operations Add-MailboxPermission,Add-RecipientPermission,Set-Mailbox 3 · WHY A PASSWORD RESET DOES NOT END IT VICTIM: password reset · sessions revoked the grant is tied to the grantee, not the victim → the grantee keeps its access REMOVE THE GRANT Get-MailboxPermission · Get-RecipientPermission list every grant on the mailbox, then remove it ATT&CK T1098.002 · Additional Email Delegate Permissions Exchange Online mailbox delegationthe row this figure is built from M365 Unified Audit Logthe log the grants land in, with MailItemsAccessed and inbox rules
Built from the Exchange Online mailbox delegation and M365 Unified Audit Log rows above.
0 matches
try a shorter term — e.g. "4624", "prefetch", "cron"
↑ top