Huntbook by Predefender

part-5

Device-centric Pivoting in Defender XDR

Author: Roger C.B. Johnsen

A device is often the first reliable anchor in an investigation. The objective is not to run one large query. Preserve the identity of the device over time, establish the relevant window, and pivot through independent evidence until the activity can be explained.

Pivot map

Starting pointNext tableStable pivotQuestion
Device nameDeviceInfoDeviceIdWhich Defender entity represents the host?
DeviceDeviceNetworkInfoDeviceIdWhich addresses and networks belonged to it?
Device or IPDeviceNetworkEventsDeviceId, IP, timeWhat communicated with it, and what did it contact?
DeviceDeviceProcessEventsProcessUniqueIdWhich process tree explains the behavior?
ProcessDeviceNetworkEventsinitiating process unique IDWhich destinations did that process use?
DeviceDeviceFileEventsDeviceId, SHA1What was created, staged, or transferred?
Device and accountDeviceLogonEventsSID, Logon IDWhich identity and session were involved?
DeviceDeviceEventsDeviceId, ActionTypeWhich security-control or specialized events add context?
AlertAlertEvidenceAlertIdWhich entities and evidence support the alert?

Prefer DeviceId over a mutable hostname, process unique identifiers over reused PIDs, and SID or object ID over display names.

Establish the device

Use the latest device snapshot to resolve an exact identifier:

let TargetName = "TARGET-DEVICE.contoso.invalid";
DeviceInfo
| where DeviceName =~ TargetName
| summarize arg_max(Timestamp, *) by DeviceId
| project Timestamp, DeviceId, DeviceName, OSPlatform,
          OSVersion, MachineGroup, DeviceType, OnboardingStatus

A partial contains match can silently combine several devices. Use it for discovery, then switch to an exact DeviceId.

Reconstruct IP history

Microsoft provides AssignedIPAddresses() for recent or point-in-time assignments:

let TargetName = "TARGET-DEVICE.contoso.invalid";
AssignedIPAddresses(TargetName)
| where IPAddress !startswith "127."
| where IPAddress !startswith "169.254."
| project Timestamp, DeviceName, IPAddress, IPType,
          NetworkAdapterType, ConnectedNetworks

For a longer history, expand the adapter snapshots:

let TargetName = "TARGET-DEVICE.contoso.invalid";
DeviceNetworkInfo
| where Timestamp > ago(30d)
| where DeviceName =~ TargetName
| mv-expand Address = todynamic(IPAddresses)
| extend IPAddress=tostring(Address.IPAddress),
         AddressType=tostring(Address.AddressType)
| where isnotempty(IPAddress)
| where IPAddress !startswith "127."
| where IPAddress !startswith "169.254."
| where NetworkAdapterType !~ "Tunnel"
| summarize FirstSeen=min(Timestamp), LastSeen=max(Timestamp),
            Records=count()
    by DeviceId, DeviceName, IPAddress, AddressType,
       NetworkAdapterName, NetworkAdapterType, MacAddress
| order by FirstSeen asc

Snapshots show observation intervals, not authoritative DHCP lease boundaries. Add a safety margin and validate with DHCP, VPN, NAC, firewall, or identity telemetry when attribution matters.

Examine both network perspectives

A known address can appear as either LocalIP or RemoteIP depending on which onboarded device reported the event.

Activity reported by the target

let TargetId = "00000000-0000-0000-0000-000000000000";
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where DeviceId == TargetId
| project Timestamp, DeviceName, ActionType,
          LocalIP, LocalPort, RemoteIP, RemotePort, RemoteUrl,
          Protocol, InitiatingProcessFileName,
          InitiatingProcessCommandLine, InitiatingProcessUniqueId
| order by Timestamp asc

Other devices reporting the target as their peer

let TargetName = "TARGET-DEVICE.contoso.invalid";
let TargetIPs =
    AssignedIPAddresses(TargetName)
    | project TargetIP=IPAddress, AssignedTime=Timestamp;
TargetIPs
| join kind=inner (
    DeviceNetworkEvents
    | where Timestamp > ago(7d)
) on $left.TargetIP == $right.RemoteIP
| where Timestamp between (AssignedTime - 1h .. AssignedTime + 1h)
| project Timestamp, ReportingDevice=DeviceName,
          LocalIP, LocalPort, TargetIP, RemotePort,
          ActionType, InitiatingProcessFileName

Local and remote fields describe the reporting device’s record; they do not always prove who initiated a session. Use ActionType, listening versus ephemeral ports, process ownership, packet or firewall data, and the peer’s telemetry. A successful TCP connection does not prove successful authentication.

Build the process story

let TargetId = "00000000-0000-0000-0000-000000000000";
let StartTime = datetime(2026-01-15T09:00:00Z);
let EndTime = StartTime + 2h;
DeviceProcessEvents
| where Timestamp between (StartTime .. EndTime)
| where DeviceId == TargetId
| project Timestamp, DeviceName, AccountSid, AccountUpn,
          ProcessUniqueId, InitiatingProcessUniqueId,
          FileName, FolderPath, ProcessCommandLine,
          InitiatingProcessFileName, InitiatingProcessCommandLine,
          SHA1, ProcessVersionInfoOriginalFileName
| order by Timestamp asc

Do not subtract every process seen on one reference device. A peer baseline is useful for ranking, but exact command-line subtraction is brittle and broad allowlists can hide living-off-the-land activity. Compare prevalence, path, signer, original filename, account, integrity, parentage, and host role.

Correlate a process with network activity

let StartTime = ago(24h);
DeviceProcessEvents
| where Timestamp >= StartTime
| where DeviceName =~ "TARGET-DEVICE.contoso.invalid"
| project ProcessTime=Timestamp, DeviceId, DeviceName,
          ProcessUniqueId, FileName, ProcessCommandLine, AccountUpn
| join kind=leftouter (
    DeviceNetworkEvents
    | where Timestamp >= StartTime
    | project NetworkTime=Timestamp, DeviceId,
              InitiatingProcessUniqueId, RemoteIP,
              RemotePort, RemoteUrl, ActionType
) on DeviceId
| where ProcessUniqueId == InitiatingProcessUniqueId
| where NetworkTime between (ProcessTime .. ProcessTime + 30m)
| project ProcessTime, NetworkTime, DeviceName, AccountUpn,
          FileName, ProcessCommandLine, RemoteIP,
          RemotePort, RemoteUrl, ActionType

Expand into files, identities, and alerts

Use the same DeviceId, time window, account SID, logon session, process identifier, URL, IP, or hash to ask focused questions:

  • Did the process create or delete a file?
  • Did a remote SMB or NFS request create the file?
  • Which logon session supplied the identity?
  • Was the account new to the device?
  • Did the file retain origin URL or referrer context?
  • Did an alert associate the device with another entity?
  • Did activity continue on a different device?

Keep joins narrow. Filter time, project only required columns, and summarize snapshot tables before joining.

Triage sequence

  1. Resolve the exact device and investigation window.
  2. Reconstruct addresses and connected networks for that window.
  3. Examine both network perspectives.
  4. Build process ancestry with stable process identifiers.
  5. Pivot to file, logon, registry, named-pipe, and alert evidence.
  6. Compare with the device role, peers, user history, and change records.
  7. Record telemetry gaps and alternative explanations.
  8. Expand fleet-wide only after identifying stable behaviors or pivots.

References

Revision

Revised DateComment
2026-07-22Article added