NanoCore: Critical Process Protection — Detection

NanoCore Image

NanoCore can mark its own process critical, so that killing it bugchecks the machine. An analyst or an EDR that terminates it takes the host down with it, which is the point. This is the hardest of the NanoCore TTPs to catch, and the detections below are honest about where the signal actually is.

MITRE ATT&CK: T1562.001

Technique write-up: NanoCore_CriticalProcess

Detection Indicators

Indicator Value
API ntdll!NtSetInformationProcess
Information class 29ProcessBreakOnTermination
Value 1, length 4
Prerequisite SeDebugPrivilege enabled first (Process.EnterDebugMode())
Prerequisite the process must already be elevated — it exits if not
Effect on kill bugcheck 0xEF CRITICAL_PROCESS_DIED

There is no direct telemetry for the call itselfNtSetInformationProcess is not audited. The realistic signals are the privilege enablement that precedes it and the bugcheck that follows a termination attempt.

Log Sources

Detection Engineering with Defender XDR

SeDebugPrivilege enabled by a process that has no business debugging anything.

// Requires 'Audit Token Right Adjusted' (Security 4703), which is off by default.
// EnabledPrivilegeList is read out of the event payload rather than assumed to exist as a
// column - the SecurityEvent schema varies by connector and agent version, so parsing the
// XML works everywhere. If your workspace does surface the column, use it directly.
SecurityEvent
| where EventID == 4703
| extend Enabled  = extract(@'Name="EnabledPrivilegeList">([^<]*)<', 1, EventData),
         TargetImg = extract(@'Name="ProcessName">([^<]*)<', 1, EventData)
| where Enabled has "SeDebugPrivilege"
| where TargetImg !has @"\Windows\System32\" and TargetImg !has @"\Windows\SysWOW64\"
| project TimeGenerated, Computer, Account, TargetImg, Enabled
| order by TimeGenerated desc

Detection Engineering with the bugcheck

There is no Sysmon signal for this TTP. The sample takes a handle to itself with Process.GetCurrentProcess().Handle and calls NtSetInformationProcess on it. Sysmon’s ProcessAccess event (EID 10) records one process opening a handle to another; a process holding a handle to itself never generates one, and the Nt call is not audited either.

What is left is the consequence. Terminating a process flagged ProcessBreakOnTermination bugchecks the host with 0xEF CRITICAL_PROCESS_DIED, so an unexpected bugcheck shortly after a termination attempt is the reliable retrospective signal:

Event
| where Source == "Microsoft-Windows-WER-SystemErrorReporting" or EventID in (1001, 41)
| where RenderedDescription has_any ("0x000000ef", "0xef", "CRITICAL_PROCESS_DIED")
| project TimeGenerated, Computer, EventID, Source, RenderedDescription
| order by TimeGenerated desc

Correlate a hit with any process your EDR or an analyst terminated in the minutes before it.

Detection Engineering with SIGMA

Full rule: sigma_nanocore_critical_process.yml

detection:
    selection:
        EventID: 4703
        EnabledPrivilegeList|contains: 'SeDebugPrivilege'
    filter_system:
        ProcessName|contains:
            - '\Windows\System32\'
            - '\Windows\SysWOW64\'
    condition: selection and not filter_system

Notes and tuning

Be realistic about coverage here. NtSetInformationProcess(ProcessBreakOnTermination) produces no event of its own. This rule catches the privilege the sample must enable first, and it needs Audit Token Right Adjusted switched on, which it is not by default.

Two complementary controls are worth more than the rule:

Disclaimer

For educational purposes only!!!