Query Details

Detect Suspicious Ncrypt Usage With Suspicious Rdp Session

Query

# *Detect Suspicious ncrypt.dll usage with RDP connections to unmanaged or non TPM protected device*

## Query Information

#### MITRE ATT&CK Technique(s)

| Technique ID | Title    | Link    |
| ---  | --- | --- |
| T1555.004 | Credentials from Password Stores: Windows Credential Manager | https://attack.mitre.org/techniques/T1555/004/ |
| T1606 | Forge Web Credentials | https://attack.mitre.org/techniques/T1606/ |
| T1021.001 | Remote Services: Remote Desktop Protocol | https://attack.mitre.org/techniques/T1021/001/ |

#### Description
This detection rule uses a WDAC audit policy to ingest missing DeviceImageLoad events in MDE, and check for suspicious processes using the ncrypt.dll and devices performing RDP connection to unmanaged or non-TPM devices. More information on the attack scenario this is detection is applicable for can be found in the references.

#### Risk
By using this detections, we can try to detect an attacker using the hellopoc.ps1 script in RoadTools to generate an assertion, and export the Windows Hello for Business keys using an RDP session. 

#### Author <Optional>
- **Name:** Robbe Van den Daele
- **Github:** https://github.com/RobbeVandenDaele
- **Twitter:** https://x.com/RobbeVdDaele
- **LinkedIn:** https://www.linkedin.com/in/robbe-van-den-daele-677986190/
- **Website:** https://hybridbrothers.com/

#### References
- https://hybridbrothers.com/detecting-non-privileged-windows-hello-abuse/
- https://github.com/dirkjanm/ROADtools/blob/master/winhello_assertion/hellopoc.ps1

## Defender XDR
```KQL
let cli_tools = dynamic(["powershell", "python"]);
// Get suspicious ncrypt.dll usage via WDAC audit policy
let time_lookback = 1h;
let no_tpm_devices = (
    ExposureGraphNodes
    // Get device nodes with their inventory ID
    | where NodeLabel == "device"
    | mv-expand EntityIds
    | where EntityIds.type == "DeviceInventoryId"
    // Get interesting properties
    | extend OnboardingStatus = tostring(parse_json(NodeProperties)["rawData"]["onboardingStatus"]),
        TpmSupported = tostring(parse_json(NodeProperties)["rawData"]["tpmData"]["supported"]),
        TpmEnabled = tostring(parse_json(NodeProperties)["rawData"]["tpmData"]["enabled"]),
        TpmActivated = tostring(parse_json(NodeProperties)["rawData"]["tpmData"]["activated"]),
        DeviceName = tostring(parse_json(NodeProperties)["rawData"]["deviceName"]),
        DeviceId = tostring(EntityIds.id)
    // Search for distinct devices
    | distinct DeviceId, DeviceName, OnboardingStatus, TpmSupported, TpmEnabled, TpmActivated
    // Get Unmanaged devices and device not supporting a TPM
    | where OnboardingStatus != "Onboarded" or (TpmSupported != "true" and TpmActivated != "true" and TpmEnabled != "true")
    | extend TpmSupported = iff(TpmSupported == "", "unknown", TpmSupported),
        TpmActivated = iff(TpmActivated == "", "unknown", TpmActivated),
        TpmEnabled = iff(TpmEnabled == "", "unknown", TpmEnabled)
);
let no_tpm_device_info = (
    DeviceNetworkInfo
    | where Timestamp > ago(7d)
    // Get latest network info for each device ID
    | summarize arg_max(Timestamp, *) by DeviceId
    | mv-expand todynamic(IPAddresses)
    | extend IPAddress = tostring(IPAddresses.IPAddress)
    // Find no TPM devices and join with their network information
    | join kind=inner no_tpm_devices on DeviceId
    | project DeviceId, DeviceName, MacAddress, IPAddress, OnboardingStatus, TpmActivated, TpmEnabled, TpmSupported
);
let dangerous_rdp_sessions = (
    DeviceNetworkEvents
    | where Timestamp > ago(time_lookback)
    // Exclude MDI RDP Connections (known for NNR)
    | where InitiatingProcessFileName !~ "microsoft.tri.sensor.exe"
    // Search for RDP connections to non-tpm devices
    | where ActionType == "ConnectionSuccess"
    | where RemotePort == 3389
    | join kind=inner no_tpm_device_info on $left.RemoteIP == $right.IPAddress
    | project-rename RemoteDeviceId = DeviceId1, 
        RdpRemoteDeviceName = DeviceName1, 
        RdpRemoteMacAddress = MacAddress, 
        RdpRemoteDeviceOnboardingStatus = OnboardingStatus, 
        RdpRemoteDeviceTpmActivated = TpmActivated, 
        RdpRemoteDeviceTpmEnabled = TpmEnabled, 
        RdpRemoteDeviceTpmSupported = TpmSupported,
        RdpTimeGenerated = Timestamp,
        RdpInitiatingProcessFileName = InitiatingProcessFileName
    | project-away IPAddress
);
// Get all possible nonce requests
let nonce_requests = (
    DeviceNetworkEvents
    | where Timestamp > ago(time_lookback)
    | where ActionType == "ConnectionSuccess"
    | where RemoteUrl =~ "login.microsoftonline.com"
    | project-rename NonceRequestTimestamp = Timestamp
);
// Get suspicious ncrypt.dll usage via WDAC audit policy
DeviceEvents
| where Timestamp > ago(time_lookback)
| where ActionType startswith "AppControl" and FileName =~ "ncrypt.dll"
// Check if the same initiating process is doing a nonce request
| join kind=inner nonce_requests on InitiatingProcessId, DeviceId
// Only flag when nonce was request 10min before of after ncrypt usage
| where Timestamp between (todatetime(NonceRequestTimestamp - 10m) .. todatetime(NonceRequestTimestamp + 10m))
// Check if the same device is doing RDP Connections
| join kind=inner dangerous_rdp_sessions on DeviceId
// Whitelist known good processes
| where InitiatingProcessFileName !in ("backgroundtaskhost.exe","svchost.exe")
// Project interesting columns
| extend WdacPolicyName = parse_json(AdditionalFields)["PolicyName"]
| project Timestamp, DeviceName, ActionType, FileName, InitiatingProcessSHA1, InitiatingProcessFileName, 
    InitiatingProcessId, InitiatingProcessAccountName, InitiatingProcessParentFileName, WdacPolicyName, InitiatingProcessRemoteSessionDeviceName, InitiatingProcessRemoteSessionIP,
    NonceRequestTimestamp, RdpTimeGenerated, RdpInitiatingProcessFileName, RdpRemoteDeviceName, RdpRemoteMacAddress, RdpRemoteDeviceOnboardingStatus,
    RdpRemoteDeviceTpmActivated, RdpRemoteDeviceTpmEnabled, RdpRemoteDeviceTpmSupported
```

## Sentinel
```KQL
let time_lookback = 1h;
let no_tpm_devices = (
    ExposureGraphNodes
    // Get device nodes with their inventory ID
    | where NodeLabel == "device"
    | mv-expand EntityIds
    | where EntityIds.type == "DeviceInventoryId"
    // Get interesting properties
    | extend OnboardingStatus = tostring(parse_json(NodeProperties)["rawData"]["onboardingStatus"]),
        TpmSupported = tostring(parse_json(NodeProperties)["rawData"]["tpmData"]["supported"]),
        TpmEnabled = tostring(parse_json(NodeProperties)["rawData"]["tpmData"]["enabled"]),
        TpmActivated = tostring(parse_json(NodeProperties)["rawData"]["tpmData"]["activated"]),
        DeviceName = tostring(parse_json(NodeProperties)["rawData"]["deviceName"]),
        DeviceId = tostring(EntityIds.id)
    // Search for distinct devices
    | distinct DeviceId, DeviceName, OnboardingStatus, TpmSupported, TpmEnabled, TpmActivated
    // Get Unmanaged devices and device not supporting a TPM
    | where OnboardingStatus != "Onboarded" or (TpmSupported != "true" and TpmActivated != "true" and TpmEnabled != "true")
    | extend TpmSupported = iff(TpmSupported == "", "unknown", TpmSupported),
        TpmActivated = iff(TpmActivated == "", "unknown", TpmActivated),
        TpmEnabled = iff(TpmEnabled == "", "unknown", TpmEnabled)
);
let no_tpm_device_info = (
    DeviceNetworkInfo
    | where TimeGenerated > ago(7d)
    // Get latest network info for each device ID
    | summarize arg_max(TimeGenerated, *) by DeviceId
    | mv-expand todynamic(IPAddresses)
    | extend IPAddress = tostring(IPAddresses.IPAddress)
    // Find no TPM devices and join with their network information
    | join kind=inner no_tpm_devices on DeviceId
    | project DeviceId, DeviceName, MacAddress, IPAddress, OnboardingStatus, TpmActivated, TpmEnabled, TpmSupported
);
let dangerous_rdp_sessions = (
    DeviceNetworkEvents
    | where TimeGenerated > ago(time_lookback)
    // Exclude MDI RDP Connections (known for NNR)
    | where InitiatingProcessFileName !~ "microsoft.tri.sensor.exe"
    // Search for RDP connections to non-tpm devices
    | where ActionType == "ConnectionSuccess"
    | where RemotePort == 3389
    | join kind=inner no_tpm_device_info on $left.RemoteIP == $right.IPAddress
    | project-rename RemoteDeviceId = DeviceId1, 
        RdpRemoteDeviceName = DeviceName1, 
        RdpRemoteMacAddress = MacAddress, 
        RdpRemoteDeviceOnboardingStatus = OnboardingStatus, 
        RdpRemoteDeviceTpmActivated = TpmActivated, 
        RdpRemoteDeviceTpmEnabled = TpmEnabled, 
        RdpRemoteDeviceTpmSupported = TpmSupported,
        RdpTimeGenerated = Timestamp,
        RdpInitiatingProcessFileName = InitiatingProcessFileName
    | project-away IPAddress
);
// Get all possible nonce requests
let nonce_requests = (
    DeviceNetworkEvents
    | where TimeGenerated > ago(time_lookback)
    | where ActionType == "ConnectionSuccess"
    | where RemoteUrl =~ "login.microsoftonline.com"
    | project-rename NonceRequestTimestamp = TimeGenerated
);
// Get suspicious ncrypt.dll usage via WDAC audit policy
DeviceEvents
| where TimeGenerated > ago(time_lookback)
| where ActionType startswith "AppControl" and FileName =~ "ncrypt.dll"
// Check if the same initiating process is doing a nonce request
| join kind=inner nonce_requests on InitiatingProcessId, DeviceId
// Only flag when nonce was request 10min before of after ncrypt usage
| where TimeGenerated between (todatetime(NonceRequestTimestamp - 10m) .. todatetime(NonceRequestTimestamp + 10m))
// Check if the same device is doing RDP Connections
| join kind=inner dangerous_rdp_sessions on DeviceId
// Whitelist known good processes
| where InitiatingProcessFileName !in ("backgroundtaskhost.exe","svchost.exe")
// Project interesting columns
| extend WdacPolicyName = parse_json(AdditionalFields)["PolicyName"]
| project TimeGenerated, DeviceName, ActionType, FileName, InitiatingProcessSHA1, InitiatingProcessFileName, 
    InitiatingProcessId, InitiatingProcessAccountName, InitiatingProcessParentFileName, WdacPolicyName, InitiatingProcessRemoteSessionDeviceName, InitiatingProcessRemoteSessionIP,
    NonceRequestTimestamp, RdpTimeGenerated, RdpInitiatingProcessFileName, RdpRemoteDeviceName, RdpRemoteMacAddress, RdpRemoteDeviceOnboardingStatus,
    RdpRemoteDeviceTpmActivated, RdpRemoteDeviceTpmEnabled, RdpRemoteDeviceTpmSupported
```

Explanation

This query is designed to detect suspicious activities involving the use of the ncrypt.dll file in conjunction with Remote Desktop Protocol (RDP) connections to devices that are either unmanaged or not protected by a Trusted Platform Module (TPM). Here's a simplified breakdown of what the query does:

  1. Identify Unmanaged or Non-TPM Devices:

    • The query first identifies devices that are either not onboarded (unmanaged) or do not support/enable/activate TPM. These devices are considered less secure.
  2. Gather Network Information:

    • It collects the latest network information for these identified devices, including their IP addresses and other relevant details.
  3. Detect RDP Connections:

    • The query looks for successful RDP connections (port 3389) to these less secure devices. It excludes known benign processes like microsoft.tri.sensor.exe from this search.
  4. Monitor ncrypt.dll Usage:

    • It tracks the usage of ncrypt.dll through Windows Defender Application Control (WDAC) audit logs. This DLL is associated with cryptographic operations and could be used maliciously.
  5. Check for Nonce Requests:

    • The query checks for connections to login.microsoftonline.com, which might indicate attempts to authenticate or generate tokens.
  6. Correlate Events:

    • It correlates the ncrypt.dll usage with nonce requests and RDP connections. Specifically, it looks for nonce requests occurring within 10 minutes before or after the ncrypt.dll usage.
  7. Filter Out Known Good Processes:

    • The query excludes known benign processes like backgroundtaskhost.exe and svchost.exe to reduce false positives.
  8. Output Relevant Information:

    • Finally, it outputs relevant details about the suspicious activities, including timestamps, device names, process details, and TPM status.

The overall goal of this query is to detect potential security threats where an attacker might be using a script (like hellopoc.ps1 from RoadTools) to exploit Windows Hello for Business keys via RDP sessions to vulnerable devices.

Details

Robbe Van den Daele profile picture

Robbe Van den Daele

Released: April 26, 2025

Tables

ExposureGraphNodesDeviceNetworkInfoDeviceNetworkEventsDeviceEvents

Keywords

DevicesRDPConnectionsTPMDeviceEventsDeviceNetworkEventsExposureGraphNodesDeviceNetworkInfoRemoteDesktopProtocolWindowsCredentialManagerWebCredentials

Operators

letdynamicmv-expandwhereextendtostringparse_jsondistinctiffsummarizearg_maxtodynamicjoinkind=innerprojectproject-renameproject-awaystartswith!~=~between..not inparse_json

Actions