> Windows Syscalls
ntoskrnl.exeT1553T1518.001T1106

NtGetCachedSigningLevel

Lee el resultado de nivel de firma cacheado por Code Integrity, almacenado como atributo extendido NTFS en un archivo.

Prototipo

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

Argumentos

NameTypeDirDescription
FileHANDLEinHandle al archivo cuyo atributo extendido $Kernel.Purge.ESBCache se leerá.
FlagsPULONGoutRecibe banderas de estado del caché (p. ej. CI_VERIFICATION_RESULT_VALID, CI_VERIFICATION_RESULT_EXPIRED).
SigningLevelPSE_SIGNING_LEVELoutRecibe el nivel de firma cacheado (0 Unchecked, 4 Authenticode, 6 Store, 8 Antimalware, 12 Microsoft, 14 Windows).
ThumbprintPUCHARoutBúfer proporcionado por el llamante que recibe el ancla de hash por-página (típicamente 32 bytes para SHA-256).
ThumbprintSizePULONGin/outEn entrada: capacidad de Thumbprint. En salida: bytes realmente escritos.
ThumbprintAlgorithmPULONGoutRecibe el ID de algoritmo BCRYPT usado para Thumbprint (p. ej. 0x800C = SHA-256).

IDs de syscalls por versión de Windows

Versión de WindowsID de syscallBuild
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

Módulo del kernel

ntoskrnl.exeNtGetCachedSigningLevel

APIs relacionadas

NtSetCachedSigningLevelWldpQueryDynamicCodeTrustGetProcessMitigationPolicy (ProcessSignaturePolicy)CiValidateFileObject

Stub del syscall

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

Notas no documentadas

La mitad *lectora* del par de caché Code Integrity — `NtSetCachedSigningLevel` escribe el EA `$Kernel.Purge.ESBCache`, esta llamada lo lee de vuelta. Llamado masivamente por `ci.dll` mismo cuando una imagen está a punto de mapearse para cortocircuitar el recorrido completo del hash Authenticode, por `wldp.dll` al evaluar la policy de dynamic-code, y por cada EDR que quiere decidir *barato* si una DLL en disco es de confianza Microsoft antes de hacer su propio scan. A diferencia del setter, el getter **no** es privilegiado: cualquier token que pueda abrir el archivo para `FILE_READ_EA` puede llamarlo. El `SigningLevel` de salida es `0` cuando el archivo nunca ha sido evaluado (el EA está ausente), así que una descarga fresca devolverá Unchecked hasta la primera carga de imagen o llamada explícita a `NtSetCachedSigningLevel`. El campo `Flags` lleva pistas de validez de caché — `CI_VERIFICATION_RESULT_EXPIRED` significa que una actualización de blocklist aguas arriba post-fecha el stamp del caché y el resultado debe re-evaluarse.

Uso común por malware

Dos patrones de abuso relevantes. Primero, **sonda de confianza** — los implantes llaman `NtGetCachedSigningLevel` contra su propia imagen y contra DLL objetivo candidatas para decidir *si deben esconderse* de un objetivo protegido por CIG. Si el resultado sobre el propio archivo del implante es `Unchecked` o `Authenticode` (4) mientras el objetivo requiere `Microsoft` (12), el loader sabe que debe abortar la inyección o pivotar a una DLL proxy firmada por Microsoft. Segundo, **evasión de allowlist DLL EDR** — los EDR modernos usan `NtGetCachedSigningLevel` para decidir qué DLL merecen escaneo runtime vs. cuáles pueden saltarse. Si un atacante puede envenenar el EA en una DLL de baja confianza para que se relea como Microsoft (la familia de investigación **CIG-bypass** que ataca `NtSetCachedSigningLevel`), cada llamada subsiguiente a `NtGetCachedSigningLevel` devuelve la mentira y la DLL queda silenciosamente allowlistada. El getter mismo también es un bloque común de cadenas estilo **PPLFault** donde el atacante necesita verificar que la escritura de caché tuvo éxito antes de continuar.

Oportunidades de detección

Por sí solas las llamadas son comunes y de baja señal — cada carga de shell-extension y camino de scan EDR las emite. La señal *interesante* es la divergencia: hashear el archivo desde una perspectiva forense, luego comparar el thumbprint en EA contra un verdadero SHA-256 de las páginas de la región authenticode del archivo. Un mismatch significa que el caché miente, que es exactamente el artefacto CIG-bypass. El `mssense.exe` de Microsoft (Defender for Endpoint) hace esta validación de forma muestreada. El provider ETW `Microsoft-Windows-CodeIntegrity` emite el evento 3089 para cada lookup, incluyendo la ruta del archivo y el nivel cacheado — la deriva entre distribuciones baseline de nivel-cacheado por ruta de archivo también puede señalar manipulación sistemática.

Ejemplos de syscalls directos

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
}

Mapeos MITRE ATT&CK

Last verified: 2026-05-20