NanoCore: Critical Process Protection — Detection
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 | 29 — ProcessBreakOnTermination |
| 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 itself — NtSetInformationProcess is not
audited. The realistic signals are the privilege enablement that precedes it and the
bugcheck that follows a termination attempt.
Log Sources
- Windows Security 4703 (token right adjusted) — requires Audit Token Right Adjusted, off by default
- Microsoft Defender XDR (
DeviceImageLoadEvents) - Sysmon EID 7 (image load)
- System log EID 1001 / 41 (bugcheck) for the after-the-fact signal
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:
- Detect the outcome. A
0xEF CRITICAL_PROCESS_DIEDbugcheck shortly after an EDR or an analyst terminated a user-mode process is a strong retrospective signal. Alert on System EID 1001 with that bugcheck code. - Do not kill it blind. Clear the flag before terminating — set
ProcessBreakOnTerminationback to0, or suspend and image the process instead.
Disclaimer
For educational purposes only!!!