> Windows Syscalls
ntoskrnl.exeT1553T1518.001T1106

NtGetCachedSigningLevel

Reads the Code Integrity cached signing-level result stored as an NTFS extended attribute on a file.

Prototype

NTSTATUS NtGetCachedSigningLevel(
  HANDLE            File,
  PULONG            Flags,
  PSE_SIGNING_LEVEL SigningLevel,
  PUCHAR            Thumbprint,
  PULONG            ThumbprintSize,
  PULONG            ThumbprintAlgorithm
);

Arguments

NameTypeDirDescription
FileHANDLEinHandle to the file whose $Kernel.Purge.ESBCache extended attribute will be read.
FlagsPULONGoutReceives cache-state flags (e.g. CI_VERIFICATION_RESULT_VALID, CI_VERIFICATION_RESULT_EXPIRED).
SigningLevelPSE_SIGNING_LEVELoutReceives the cached signing level (0 Unchecked, 4 Authenticode, 6 Store, 8 Antimalware, 12 Microsoft, 14 Windows).
ThumbprintPUCHARoutCaller-supplied buffer that receives the per-page hash anchor (typically 32 bytes for SHA-256).
ThumbprintSizePULONGin/outOn input: capacity of Thumbprint. On output: bytes actually written.
ThumbprintAlgorithmPULONGoutReceives the BCRYPT algorithm ID used for Thumbprint (e.g. 0x800C = SHA-256).

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070xE1win10-1507
Win10 16070xE4win10-1607
Win10 17030xE7win10-1703
Win10 17090xE8win10-1709
Win10 18030xE9win10-1803
Win10 18090xEAwin10-1809
Win10 19030xEBwin10-1903
Win10 19090xEBwin10-1909
Win10 20040xF0win10-2004
Win10 20H20xF0win10-20h2
Win10 21H10xF0win10-21h1
Win10 21H20xF1win10-21h2
Win10 22H20xF1win10-22h2
Win11 21H20xF6win11-21h2
Win11 22H20xF7win11-22h2
Win11 23H20xF7win11-23h2
Win11 24H20xF9win11-24h2
Server 20160xE4winserver-2016
Server 20190xEAwinserver-2019
Server 20220xF5winserver-2022
Server 20250xF9winserver-2025

Kernel module

ntoskrnl.exeNtGetCachedSigningLevel

Related APIs

NtSetCachedSigningLevelWldpQueryDynamicCodeTrustGetProcessMitigationPolicy (ProcessSignaturePolicy)CiValidateFileObject

Syscall stub

4C 8B D1                  mov r10, rcx
B8 F9 00 00 00            mov eax, 0xF9
F6 04 25 08 03 FE 7F 01   test byte ptr [0x7FFE0308], 1
75 03                     jne short +3
0F 05                     syscall
C3                        ret
CD 2E                     int 2Eh
C3                        ret

Undocumented notes

The *reader* half of the Code Integrity cache pair — `NtSetCachedSigningLevel` writes the `$Kernel.Purge.ESBCache` EA, this call reads it back. Called extensively by `ci.dll` itself when an image is about to be mapped to short-circuit the full Authenticode hash walk, by `wldp.dll` when evaluating dynamic-code policy, and by every EDR that wants to *cheaply* decide whether a DLL on disk is Microsoft-trusted before doing its own scan. Unlike the setter, the getter is **not** privileged: any token that can open the file for `FILE_READ_EA` can call it. The output `SigningLevel` is `0` when the file has never been evaluated (the EA is absent), so a fresh download will return Unchecked until the first image load or explicit `NtSetCachedSigningLevel` call. The `Flags` field carries cache-validity hints — `CI_VERIFICATION_RESULT_EXPIRED` means an upstream blocklist update post-dates the cache stamp and the result should be re-evaluated.

Common malware usage

Two relevant abuse patterns. First, **trust probe** — implants call `NtGetCachedSigningLevel` against their own image and against candidate target DLLs to decide *whether they should hide* from a CIG-protected target. If the result on the implant's own file is `Unchecked` or `Authenticode` (4) while the target requires `Microsoft` (12), the loader knows to abort the injection or pivot to a Microsoft-signed proxy DLL. Second, **EDR DLL allowlist evasion** — modern EDRs use `NtGetCachedSigningLevel` to decide which DLLs deserve runtime scanning vs. which can be skipped. If an attacker can poison the EA on a low-trust DLL to read back as Microsoft (the **CIG-bypass** family of research that targets `NtSetCachedSigningLevel`), every subsequent `NtGetCachedSigningLevel` call returns the lie and the DLL is silently allowlisted. The getter itself is also a common building block of **PPLFault**-style chains where the attacker needs to verify the cache write succeeded before continuing.

Detection opportunities

On its own, calls are common and low-signal — every shell-extension load and EDR scan path issues them. The *interesting* signal is divergence: hash the file from a forensic perspective, then compare the in-EA thumbprint against an actual SHA-256 of the file's authenticode-region pages. A mismatch means the cache lies, which is exactly the CIG-bypass artifact. Microsoft's `mssense.exe` (Defender for Endpoint) does this validation on a sampled basis. The `Microsoft-Windows-CodeIntegrity` ETW provider emits event 3089 for each lookup, including the file path and the cached level — drift between population-baseline cached-level distributions per file path can also flag systematic tampering.

Direct syscall examples

cTrust probe before staging into a CIG-protected target

// Implant decides whether to inject based on the cached signing level
// of its own DLL versus the requirement implied by the target's
// ProcessSignaturePolicy.
#include <windows.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI* pNtGetCachedSigningLevel)(
    HANDLE, PULONG, PUCHAR /*SigningLevel*/, PUCHAR, PULONG, PULONG);

int own_signing_level(LPCWSTR ownDllPath) {
    HANDLE h = CreateFileW(ownDllPath, FILE_READ_EA | SYNCHRONIZE,
                           FILE_SHARE_READ, NULL, OPEN_EXISTING,
                           FILE_ATTRIBUTE_NORMAL, NULL);
    ULONG flags = 0, thumbSize = 32, thumbAlg = 0;
    UCHAR thumb[32] = { 0 };
    UCHAR level = 0;

    pNtGetCachedSigningLevel f = (pNtGetCachedSigningLevel)GetProcAddress(
        GetModuleHandleA("ntdll.dll"), "NtGetCachedSigningLevel");
    f(h, &flags, &level, thumb, &thumbSize, &thumbAlg);

    CloseHandle(h);
    return level;  // 0 Unchecked, 4 Authenticode, 8 Antimalware, 12 Microsoft, 14 Windows
}

asmx64 direct stub (Win11 24H2 / Server 2025, SSN 0xF9)

NtGetCachedSigningLevel PROC
    mov  r10, rcx
    mov  eax, 0F9h
    syscall
    ret
NtGetCachedSigningLevel ENDP

rustRead cached level on every newly downloaded payload

// Used by post-exploitation tooling to decide if a downloaded module is
// already trusted (often it is not — the EA only populates on first load).
use std::ffi::c_void;
use windows_sys::Win32::Foundation::HANDLE;

extern "system" {
    fn NtGetCachedSigningLevel(
        file: HANDLE, flags: *mut u32, level: *mut u8,
        thumb: *mut u8, thumb_size: *mut u32, thumb_alg: *mut u32) -> i32;
}

unsafe fn cached_level(file: HANDLE) -> u8 {
    let mut f = 0u32; let mut l = 0u8;
    let mut t = [0u8; 32]; let mut ts = 32u32; let mut ta = 0u32;
    NtGetCachedSigningLevel(file, &mut f, &mut l, t.as_mut_ptr(), &mut ts, &mut ta);
    l
}

MITRE ATT&CK mappings

Last verified: 2026-05-20