Inizializzazione SAC

()

Divideremo questa sezione in due fasi. La prima fase parleremo di SAC durante il caricatore di Windows. La seconda fase parleremo di SAC durante l’inizializzazione del sistema operativo. È importante comprendere che sia il caricatore che il sistema operativo svolgono un ruolo nell’abilitazione di SAC. Infine, aggiungerò una sezione per spiegare come funziona la protezione dei valori nella sottochiave CI\Protected.
Il seguente semplice diagramma mostra ad alto livello il flusso di inizializzazione SAC per le fasi summenzionate.

Diagramma di inizializzazione globale del SAC

SAC durante Winload

In questa sezione discuteremo come viene scelta la politica SAC per lo stato SAC attivo, come la persistenza e la coerenza tra RegKeys viene applicata da Winload e come la politica SAC viene passata al kernel.
Il diagramma seguente presenta una rappresentazione di alto livello di ciò che vedremo in questa sezione.

alt img

Il primo passaggio per l’inizializzazione del SAC avviene all’inizio del processo di caricamento del sistema operativo. Più precisamente subito dopo il caricamento del SystemHive durante la preparazione del target ( OslPrepareTarget). La funzione OslpProcessSIPolicyverrà chiamata per elaborare le politiche di integrità del sistema. All’interno di questa funzione le politiche condizionali – SKU, EMode, SAC Enforce, SAC Evaluation – verranno valutate per vedere se devono essere ignorate o sbloccate. Microsoft considera queste quattro politiche condizionali perché possono essere Ignora/Sbloccato, a differenza di altre politiche come la “Politica del driver di MS Windows” che si applica sempre. I policiesGUID per le politiche condizionali sono archiviati in un array globale definito dal simbolo g_SiConditionalPolicies.

La differenza tra Ignora e Sblocca è molto sottile. Il flag di sblocco sarà sempre selezionato. D’altra parte, il flag Ignora verrà selezionato solo per i criteri in cui “Enabled: Unsigned System Integrity Policy” non è impostato. Al momento Ignora e Sblocca la partita 1 a 1.

Per determinare se SAC deve essere abilitato per Enforce o Evaluation, vengono utilizzate le due funzioni seguenti.

  • OslpShouldIgnoreUnlockableNightsWatchDesktopEnforcePolicy
  • OslpShouldIgnoreUnlockableNightsWatchDesktopEvalPolicy

Questa è la prima volta che vedremo il riferimento Nights Watch per denotare SAC, questo sembra essere il nome interno di Microsoft.

Queste due funzioni si comportano allo stesso modo, con l’unica differenza che forniscono un PolicyGUID diverso alla funzione di valutazione interna:

bool  // Return value indicates if unlockable or not
OslpShouldIgnoreUnlockableNightsWatchDesktopPolicy(
  PGUID PolicyGUID, // The PolicyGUID matches the name of the `.cip` files we mentioned above
  HANDLE SystemHive,
  PBOOL Active, // If true policy is active 
  PBOOL Ignore  // If true policy should be ignored
  );

Questa funzione utilizza il parametro PolicyGUID per determinare quale stato SAC controllare. Chiama OslpGetNightsWatchDesktopRegKeyState, che restituisce lo stato SAC effettivo nella macchina. Se lo stato attuale del SAC corrisponde a quello in fase di valutazione, questa politica è considerata attiva: si tratta di una semplificazione eccessiva. Ci sono alcuni altri controlli come se il dispositivo fosse WinPE o se fosse richiesta una policy firmata. Questi controlli possono rendere la funzione Ignora e sbloccabile anche se il registro indica che SAC è attivo.

OslpGetNightsWatchDesktopRegKeyStateVale la pena dare un’occhiata al comportamento di . Questa routine è responsabile di mantenere SAC abilitato durante i riavvii e di mantenere la coerenza tra entrambi i valori di registro. Questa routine ha quattro possibili scenari:

  • VerifiedAndReputablePolicyState == VerifiedAndReputablePolicyStateMinValueSeen : i valori sono gli stessi, quindi restituire il valore direttamente.
  • VerifiedAndReputablePolicyState VerifiedAndReputablePolicyStateMinValueSeen : durante la precedente sessione di avvio lo stato SAC è stato modificato. Restituiamo il valore da VerifiedAndReputablePolicyStatee aggiorniamo il valore in ProtectedSubKey.
  • VerifiedAndReputablePolicyState VerifiedAndReputablePolicyStateMinValueSeen : questo è un caso d’angolo, poiché VerifiedAndReputablePolicyStatenon dovrebbe mai essere maggiore del valore sotto la Protectedchiave. Credo che questo sia qui per mantenere la coerenza tra entrambi i valori se qualcuno modifica manualmente il valore VerifiedAndReputablePolicyState.
  • Entrambi i valori sono 3 o più: indica una transizione di stato non valida e la funzione fallirà.

Il seguente pseudo-codice lo riassume.

...
Status = OslGetDWordValue(SystemHive, PolicySubkey, L"VerifiedAndReputablePolicyState", &NWState);
Status = OslGetDWordValue(SystemHive, ProtectedSubkey, L"VerifiedAndReputablePolicyStateMinValueSeen", &NWMinValSeen);

if ( NT_SUCCESS( Status ) ) {
  if ( NWState <= NWMinValSeen ) {
    *SACState = NWState;
    if ( NWState < 3 ) {

      if ( NWState >= NWMinValSeen )
        return STATUS_SUCCESS;

      return OslHiveReadWriteDword( SystemHive, 1, ProtectedSubkey, L"VerifiedAndReputablePolicyStateMinValueSeen", SACState );
    }
  } 
  else {
    *SACState = NWMinValSeen;
    if ( NWMinValSeen <= 2 ) 
      return OslHiveReadWriteDword( SystemHive, 1, PolicySubkey, L"VerifiedAndReputablePolicyState", SACState );
  }
  return STATUS_INVALID_STATE_TRANSITION;
}

Quando si verifica una modifica dello stato SAC tramite l’app di sicurezza. Il sistema operativo scriverà nel file VerifiedAndReputablePolicyState. Dopo il riavvio dell’utente, questo stato sarà persistente nella macchina. Ciò significa che dopo una transizione di stato SAC è ancora possibile modificare VerifiedAndReputablePolicyStatee la transizione non sarà persistente al riavvio successivo. Questo mi fa pensare che MS attiverà le transizioni dalla modalità di valutazione solo durante l’installazione degli aggiornamenti o chiederanno un riavvio. Ovviamente, durante la sessione in cui c’è una transizione di stato SAC, le politiche attive verranno aggiornate.

Una volta che tutte le politiche condizionali sono state controllate per vedere se sono sbloccabili o dovrebbero essere ignorate. I valori ottenuti da ciascuna funzione verranno scritti nei seguenti due globali:

  • g_SIPolicyConditionalPolicyConditionUnlockHasBeenMet
  • g_SIPolicyConditionalPolicyConditionIgnoreHasBeenMet

Il valore scritto in questi globali è una matrice di quattro byte che può essere rappresentata con la struttura seguente

typedef struct _SI_POLICY_MODES {
  BOOLEAN SkuPolicy;    // Policy used when Windows is running in S mode 
  BOOLEAN EModePolicy;  // I can't find any information about EMode, please reach out if you know more
  BOOLEAN NightsWatchDesktopEnforce;
  BOOLEAN NightsWatchDesktopEval;
} SI_POLICY_MODES, *PSI_POLICY_MODES;

Successivamente, il caricatore proverà ad analizzare i file delle politiche. Innanzitutto caricando .cipin memoria i dati serializzati da ogni file (vedi BlSIPolicyGetAllPolicyFiles). Quindi analizzare i dati da ogni file all’interno SIPolicyParsePolicyData: se qualcuno è interessato ai dettagli, controlla SIPolicyInitializecome ogni sezione della politica viene analizzata in una struttura. Nella parte 2 parleremo di più di questa struttura e dei suoi dati.

Una volta che la politica è stata analizzata, verranno verificate sia le condizioni Ignora che Sblocco per vedere se sono soddisfatte. Nel caso in cui una condizione sia soddisfatta, la polizza verrà scartata. Nel caso in cui nessuna delle condizioni sia soddisfatta, la politica verrà impostata come attiva utilizzando la funzione SIPolicySetAndUpdateActivePolicy.

Nel caso in cui l’opzione della politica “Enabled: Unsigned System Integrity Policy” sia impostata, la PolicyVersion e i PolicySignersData verranno eliminati dallo spazio dei nomi privato di EFI SecureBoot. Il nome della variabile eliminata sarà composto concatenando il PolicyGUID più la stringa PolicyVersion/PolicySignersData: queste variabili EFI vengono create solo quando PolicyOptions ha “Enabled:Unsigned System Integrity Policy” disabilitato.

Nell’output seguente possiamo vedere come viene chiamato SetVariable con dimensione 0 che causerebbe l’eliminazione della variabile se trovata.

[SetVariable][VendorGUID: 77FA9ABD-0359-4D32-BD60-28F4E78F784B] Variable: "{0283ac0f-fff1-49ae-ada1-8a933130cad6}PolicyVersion" Size: "0x00000000" Attributes: "0x00000000"  Status: EFI_NOT_FOUND
[SetVariable][VendorGUID: 77FA9ABD-0359-4D32-BD60-28F4E78F784B] Variable: "{0283ac0f-fff1-49ae-ada1-8a933130cad6}PolicyUpdateSigners" Size: "0x00000000" Attributes: "0x00000000"  Status: EFI_NOT_FOUND

Per entrambe le politiche SAC verrà cancellata qualsiasi variabile EFI. Successivamente, la politica verrà impostata come attiva chiamando SIPolicySetActivePolicy. Questa chiamata aggiungerà la politica in un nodo che sarà collegato alla variabile globale g_SiPolicyCtx. Verrà g_NumberOfSiPoliciesincrementato di conseguenza e un handle per il nuovo criterio verrà archiviato in g_SiPolicyHandles– Questa variabile è una matrice di 32 handle, poiché WDAC supporta fino a 32 criteri attivi su un dispositivo contemporaneamente.

Il prototipo della SI_POLICY_CTXstruttura che è custodita g_SiPolicyCtxè il seguente:

typedef struct _SI_POLICY_CTX {
  PSI_POLICY Policy;
  PSI_POLICY BasePolicy;
  GUID PolicyGUID;
  GUID BasePolicyGUID;
  bool IsBasePolicy;
  bool AuditModeEnabled;
  PSI_POLICY_CTX RootNode;
  PSI_POLICY_CTX NextNode;
  PSI_POLICY_CTX SupplementalPolicy;
} SI_POLICY_CTX, *PSI_POLICY_CTX;

E l’immagine seguente mostra le tre variabili globali. Nel mio caso ci sono tre politiche attive, una delle quali è una politica supplementare per la politica di applicazione della SAC: le politiche supplementari aiutano ad espandere la politica di base per aumentare il cerchio di fiducia della politica.

alt img

Con queste informazioni, il caricatore sarà in grado di costruire la struttura CI all’interno del blocco di parametri del caricatore . Questo viene fatto all’interno della funzione OslBuildCodeIntegrityLoaderBlock. Questa routine, tra molte altre cose, otterrà la dimensione delle politiche SI serializzate con l’aiuto della funzione SIPolicyGetSerializedPoliciesSize. Il codice utilizzerà le variabili globali g_NumberOfSiPoliciesg_SiPolicyHandlesle dimensioni e verranno archiviate nel campo CodeIntegrityPolicySize di LOADER_PARAMETER_CI_EXTENSION. Successivamente, i dati serializzati verranno copiati tramite la funzione SIPolicyGetSerializedPolicies. L’offset di questi dati sarà memorizzato nel campo CodeIntegrityPolicyOffset . Queste informazioni, insieme ad altre informazioni CI, verranno archiviate nei campi CodeIntegrityDataSize e CodeIntegrityDatadi LOADER_PARAMETER_EXTENSION– Il blocco parametri del caricatore viene passato come argomento quando il caricatore passa al sistema operativo .

Sì, verrà copiato solo il payload serializzato. Immagino che tutta l’analisi della politica eseguita in precedenza sia principalmente per verificare che la politica sia valida e attivare un SYSTEM_INTEGRITY_POLICYerrore se non è valida. Potenzialmente anche per utilizzare i valori delle politiche per l’attestazione o le variabili EFI.

E questo è praticamente tutto ciò che vedremo per l’inizializzazione del SAC durante il winload – In un post futuro potremmo rivisitare il winload per parlare di SiPolicies e MeasuredBoot, PCR, ecc…
La seguente acquisizione mostra come verranno impostati questi dati prima del passaggio al sistema operativo.

alt img

SAC durante OS Init

In questa sezione daremo una rapida occhiata a come il kernel inizializza il CI. Successivamente, esamineremo il modo in cui il CI inizializza le politiche fornite da Winload. E infine, come da queste politiche determina se SAC è abilitato ad agire di conseguenza.

alt img

Durante l’inizializzazione del sistema operativo, più precisamente durante la Fase 1. Il kernel chiamerà il metodo CiInitialize(Exported by the ci.dll). Questa funzione verrà utilizzata principalmente dal kernel e dalla CI per lo scambio di API. Il kernel riceve SeCiCallbacks, che contiene i puntatori alle funzioni che il kernel utilizzerà per interagire con l’elemento della configurazione. D’altra parte, la CI DLL riceve SeCiPrivateApis, che contiene, tra le altre funzioni del kernel, l’interfaccia VSL HVCI in modo che il CI possa attivare Hypercall tramite il kernel durante l’esecuzione di qualsiasi convalida HVCI. Il kernel passerà anche le opzioni iniziali di CodeIntegrity. Queste opzioni sono state create dal caricatore di Windows e archiviate nel fileLOADER_PARAMETER_CI_EXTENSION. Queste opzioni inizialmente conterranno elementi come le opzioni CodeIntegrity BCD (DisableIntegrityChecks, AllowPrereleaseSignatures, AllowFlightSignatures) e le impostazioni WHQL. Le opzioni CI sono archiviate nella variabile globale g_CiOptionse CI le aggiornerà anche in base alle informazioni recuperate dal sistema operativo e dai criteri.

Dichiarazione di non responsabilità: il CI è una bestia a sé stante e lo scopo di questo post è capire come funziona SAC non come funziona l’intero CI. Mi concentrerò principalmente sul SAC e su ciò che è necessario affinché funzioni. Impareremo ancora molto sul CI, ma lascerò molte cose da parte. Ad un certo punto potrei fare un post completo sul CI

Sempre durante la Fase 1 del sistema operativo, il kernel chiamerà CiInitializePolicytutti i callback della CI. Questa routine riceverà come primo parametro il file LOADER_PARAMETER_CI_EXTENSION. La routine chiamerà la sua controparte privata CipInitializeSiPolicy. La funzione chiamerà SIPolicyInitializeFromSerializedPoliciesper convalidare, analizzare e caricare in memoria la politica serializzata dall’estensione CI del parametro del caricatore. Come per winload, se l’analisi della politica funziona correttamente la politica verrà aggiunta a g_SiPolicyHandlesg_SiPolicyCtx. Ancora più importante, se le politiche serializzate vengono analizzate correttamente, CipUpdateCiSettingsFromPoliciesverrà chiamata la funzione. Questo metodo aggiorna le impostazioni dell’elemento della configurazione globale in base alle PolicyRules di ciascuna policy. All’interno di questa funzione il CI verificherà se SAC è abilitato chiamando SIPolicyNightsWatchEnabled.

1: kd> k
 # Child-SP          RetAddr               Call Site
00 ffff998b`4b406748 fffff800`356858b4     CI!SIPolicyNightsWatchEnabled
01 ffff998b`4b406750 fffff800`35683ba0     CI!CipUpdateCiSettingsFromPolicies+0x224
02 ffff998b`4b406800 fffff800`35622f94     CI!CipInitializeSiPolicy+0x24
03 ffff998b`4b406830 fffff800`356626fc     CI!CiInitializePolicyFromPolicies+0x278
04 ffff998b`4b4068a0 fffff800`32b5766c     CI!CiInitializePolicy+0x28c
05 ffff998b`4b4069d0 fffff800`32b2a59b     nt!SeCodeIntegrityInitializePolicy+0x70
06 ffff998b`4b406a00 fffff800`32825d43     nt!Phase1InitializationDiscard+0xb0f
07 ffff998b`4b406bb0 fffff800`322c3977     nt!Phase1Initialization+0x23
08 ffff998b`4b406bf0 fffff800`32423bb4     nt!PspSystemThreadStartup+0x57
09 ffff998b`4b406c40 00000000`00000000     nt!KiStartSystemThread+0x34

Questa funzione è interessante e possiamo finalmente iniziare a dare un’occhiata alla struttura della politica SI. Quello che farà la funzione sarà call SIPolicyQueryOneSecurityPolicy. Questa routine ha il seguente prototipo:

NTSTATUS 
SIPolicyQueryOneSecurityPolicy(
  PSI_POLICY SiPolicy,
  PCUNICODE_STRING Provider,
  PCUNICODE_STRING Key,
  PCUNICODE_STRING ValueName,
  PDWORD Type,
  PVOID Data,
  PULONG RetLen
  );

Questo metodo sarà abbastanza ricorrente quando si tratta di politiche SI. Poiché viene utilizzato per controllare/ottenere le impostazioni di sicurezza impostate in una politica. La struttura delle politiche (ho chiamato personalmente questa struttura SI_POLICY) ha i seguenti due membri: SecureSettingsCount & SecureSettingsData .

typedef struct _SECURE_SETTINGS_DATA {
  INT32 Type;
  UNICODE_STRING Provider;
  UNICODE_STRING Key;
  UNICODE_STRING ValueName;
  union {
    bool Boolean;               // Type 0
    INT32 Int;                  // Type 1
    POLICY_BINARY_DATA Binary;  // Type 2
    UNICODE_STRING String;      // Type 3
  } Data;
} SECURE_SETTINGS_DATA, *PSECURE_SETTINGS_DATA;

typedef struct _SI_POLICY {
  ...
  INT32 SecureSettingsCount;                  // offset 0x698

  _Field_size_(SecureSettingsCount * sizeof(SECURE_SETTINGS_DATA))
  PSECURE_SETTINGS_DATA SecureSettingsData[];   // offset 0x6A0
  ...
} SI_POLICY, *PSI_POLICY;

Quando il criterio serializzato viene analizzato, la memoria necessaria per tutte le impostazioni sicure verrà allocata e archiviata nel puntatore SecureSettingsData . Ogni volta che l’elemento della configurazione deve eseguire una query su un’impostazione di sicurezza, chiamerà SIPolicyQueryOneSecurityPolicyil provider, la chiave e il nome valore che deve cercare. Internamente la funzione memorizzerà questi tre valori in una struttura che verrà utilizzata come chiave nella funzione bsearch . La base per la ricerca sarà impostata sui SecureSettingsData della policy. La funzione Compare è impostata su SIPolicySecureSettingSearchCompare. La funzione Compare cercherà di far corrispondere Provider, Key e ValueName da SECURE_SETTINGS_DATAquelli che vengono interrogati. Il confronto per ogni valore viene effettuato utilizzando RtlCompareUnicodeString.

Nel nostro caso, quando si cerca di vedere se SAC è abilitato – Inside SIPolicyNightsWatchEnabled– i valori passati alla funzione di query saranno i seguenti:

  • Fornitore: Microsoft
  • Chiave: Impostazioni di WindowsLockdownPolicy
  • ValueName: VerifiedAndReputableTrustMode

Nel caso in cui l’impostazione sicura venga trovata nel criterio, SAC viene considerato abilitato e il valore NW_ENABLED (0x4000)verrà impostato nel file g_CiPolicyState.

Questi valori sono presenti anche nel formato XML della Policy. Se controlli Enforce and Evaluation XML dall’appendice, vedrai che questa impostazione di sicurezza è impostata su true in entrambi.

Solo per il completamento, PolicyState è un campo di bit che può assumere i seguenti valori (alcuni sono mancanti) – Questi sono per lo più presi dai metadati dell’evento ETW dalla funzioneCiInstrumentSiPolicyInfo

typedef enum _CI_POLICY_STATE {
  NEED_TO_APPLY_TO_CI = 0x1,
  NEED_TO_APPLY_TO_UMCI = 0x2,
  AUDIT_MODE_ENABLED = 0x4,
  REQUIRES_WHQL = 0x8,
  REQUIRES_EV_WHQL = 0x10,
  INVALIDATE_EA_ON_REBOOT = 0x20,
  PER_PROCESS_VALIDATION = 0x40,
  FORCE_IMAGE_REVALIDATION = 0x80,
  FULL_IMAGE_PATH_AND_MACROS = 0x400,
  UMCI_AUDIT_ONLY = 0x800,
  UMCI_OPT_FOR_EXPIRED = 0x1000,
  AUTH_ROOT_AUTHORIZED = 0x2000,
  NIGHTS_WATCH = 0x4000,
  SMART_LOCKER = 0x8000,
  REQUEST_AUTH_ATTRS = 0x10000,
  APPID_TAGGING = 0x20000,
} CI_POLICY_STATE, *PCI_POLICY_STATE;

L’inquadratura seguente mostra lo stato subito prima di chiamare SIPolicyQueryOneSecurityPolicycon SIPolicyNightsWatchEnabledla politica di applicazione del SAC utilizzata per la query: scusa per il tema chiaro, la dxgriglia non sembra molto bene con il tema scuro.

alt img

Tornando in CiInitializePolicy, una variabile globale per indicare il valore minimo visto per SAC in questa sessione di avvio verrà aggiornata nel modo seguente:

alt img

Fondamentalmente, nel caso in cui SAC sia abilitato, la variabile locale EnforceNWverrà impostata con il PolicyGUID della policy di applicazione SAC. Questo GUID viene quindi passato alla funzione SIPolicyIsPolicyActive. Se questa funzione restituisce true (1), il codice sottrarrà “2-1” impostando lo g_NightsWatchDesktopMinValueSeenDuringThisBootSessionstato su Enforce. Nel caso in cui il criterio SAC Enforce non sia attivo ma SAC sia abilitato. La funzione restituisce false(0), quindi il valore memorizzato nel globale sarà “2-0” impostando lo Evaluationstato. Infine se SAC non è abilitato, il valore memorizzato nel globale è 0 (stato Off).

Nella parte 2 vedremo come CI gestisce le transizioni di stato SAC che vengono attivate quando si modifica lo stato nell’app di sicurezza di Windows. Piccolo spoiler: si tratta di gestire un segnale inviato dal Defender,

/ 5
Grazie per aver votato!

How useful was this post?

Click on a star to rate it!

Average rating / 5. Vote count:

No votes so far! Be the first to rate this post.

As you found this post useful...

Follow us on social media!

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?