Recherche sur le système de licence client dans le noyau Windows exposé à partir de la classe SystemPolicyInformation
dans l'appel système NtQuerySystemInformation
.
Il existe deux principaux services en mode utilisateur qui interagissent directement avec les licences client : clipc.dll
(Client Licensing Platform Client) et clipsvc.dll
(Client License Service). L'image du noyau qui gère les requêtes de licence client est clipsp.sys
(Client License System Policy). Comme l'accent est mis sur les éléments internes des routines de licence dans le noyau, peu de choses seront mentionnées sur les services du mode utilisateur.
Le client démarre le service de licence via le gestionnaire de services et communique avec le service via des appels de procédure à distance (RPC). Le service enregistre plusieurs gestionnaires utilisés dans l'API Software Licensing.
Les gestionnaires qui doivent s'interfacer avec les informations de licence du noyau appelleront NtQuerySystemInformation
avec la classe d'informations SystemPolicyInformation
. La structure de classe SystemPolicyInformation
pour le transfert de données a été documentée par Geoff Chappell. La structure est présentée ci-dessous :
typedef struct _SYSTEM_POLICY_INFORMATION
{
PVOID InputData;
PVOID OutputData;
ULONG InputSize;
ULONG OutputSize;
ULONG Version;
NTSTATUS Status;
} SYSTEM_POLICY_INFORMATION, * PSYSTEM_POLICY_INFORMATION;
La page de référence dans MSDN pour la classe d'information offre une structure encore plus incomplète et suggère d'utiliser l'API SL de niveau supérieur. En tant que telles, les structures internes utilisées par le noyau ne sont pas documentées. Chaque structure interne documentée dans mes recherches a fait l'objet d'une ingénierie inverse et a été nommée en fonction de son utilisation déduite et peut ne pas refléter avec précision l'utilisation interne réelle.
Une brève ingénierie inverse des gestionnaires de licences ClipSVC révèle que les structures d'entrée et de sortie sont cryptées et déchiffrées à l'aide d'un chiffre implémenté par l'obfuscateur interne de Microsoft : WarBird.
// setup decryption cipher and key
pDecryptionCipher = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0xA0 );
if ( !pDecryptionCipher )
goto LABEL_2;
decryptionCipher = pDecryptionCipher;
*pDecryptionCipher = `WarbirdUmGetDecryptionCipher ' ::`2 ' ::DecryptionCipher[ 0 ];
pDecryptionCipher[ 1 ] = `WarbirdUmGetDecryptionCipher ' ::`2 ' ::DecryptionCipher[ 1 ];
pDecryptionCipher[ 2 ] = `WarbirdUmGetDecryptionCipher ' ::`2 ' ::DecryptionCipher[ 2 ];
pDecryptionCipher[ 3 ] = `WarbirdUmGetDecryptionCipher ' ::`2 ' ::DecryptionCipher[ 3 ];
pDecryptionCipher[ 4 ] = `WarbirdUmGetDecryptionCipher ' ::`2 ' ::DecryptionCipher[ 4 ];
pDecryptionCipher[ 5 ] = `WarbirdUmGetDecryptionCipher ' ::`2 ' ::DecryptionCipher[ 5 ];
pDecryptionCipher[ 6 ] = `WarbirdUmGetDecryptionCipher ' ::`2 ' ::DecryptionCipher[ 6 ];
pDecryptionCipher[ 7 ] = `WarbirdUmGetDecryptionCipher ' ::`2 ' ::DecryptionCipher[ 7 ];
pDecryptionCipher[ 8 ] = `WarbirdUmGetDecryptionCipher ' ::`2 ' ::DecryptionCipher[ 8 ];
pDecryptionCipher[ 9 ] = `WarbirdUmGetDecryptionCipher ' ::`2 ' ::DecryptionCipher[ 9 ];
pDecryptionKey = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 8 );
if ( !pDecryptionKey )
{
LABEL_2:
ReturnStatus = STATUS_NO_MEMORY;
goto END;
}
decryptionKey = pDecryptionKey;
*pDecryptionKey = `WarbirdUmGetDecryptionKey ' ::`2 ' ::nDecryptionKey;
Microsoft WarBird a fait l'objet de recherches au cours des années précédentes et expose différentes passes d'obscurcissement, notamment l'obscurcissement de la machine virtuelle, l'empaquetage de code et même des fonctionnalités intégrées au noyau Windows pour décrypter et exécuter des charges utiles signées sur le tas à l'aide d'un chiffrement de Feistel en exposant une classe d'informations système spéciale. : SystemControlFlowTransition
.
La structure interne analysée par le noyau se compose de trois blocs de données contenant les données cryptées, les arguments de décryptage du chiffre WarBird et une clé de décryptage de 64 bits. Les données d'entrée déchiffrées contiennent le type d'informations de politique, le nombre d'arguments, ainsi que les arguments de chiffrement et la clé pour chiffrer les données. Une somme de contrôle XOR pour les données déchiffrées est ajoutée aux données cryptées et est vérifiée après le décryptage. Le bloc de chiffrement de déchiffrement est formaté avec des arguments pour les sous-programmes de chiffrement positionnés en haut et des arguments en bas. Le noyau transmettra les paramètres dans l'ordre inverse pendant 16 itérations. Les clés et les arguments de chiffrement sont codés en dur en fonction de la classe de stratégie.
// +-------------------+ +-------------------+
// | Size | | | Block B
// +-------------------+ +-------------------+ ------------ 0x0
// | | | | ^
// | | | 0xC998E51B | |
// | Function Args | | 0xA3A9632E | |
// | 8 bytes | | | | 128 bytes
// | | | | |
// | | | 0x00000000 | |
// | | | | |
// +-------------------+ +-------------------+ ---------- 0x7E
// | | | |
// | Fn Ptr Index | | 0x050B1902 | ^ 32 bytes
// | 2 bytes | | 0x1F1F1F1F | | 0x9E
// +-------------------+ +-------------------+ | ---------- 0xA0
typedef struct _WB_CIPHER
{
UCHAR FnArgs[ 128 ];
UCHAR FnIndex[ 32 ];
} WB_CIPHER, * WB_CIPHER;
typedef struct PWB_KEY
{
ULONGLONG Key;
} WB_KEY, * PWB_KEY;
typedef struct _SP_ENCRYPTED_HEADER
{
UCHAR EncryptedBody[ SP_DATA_SIZE ];
ULONGLONG XorChkKey;
} SP_ENCRYPTED_HEADER;
typedef struct _SP_ENCRYPTED_DATA
{
ULONG EncryptedDataSize;
SP_ENCRYPTED_HEADER EncryptedHeaderData;
ULONG DecryptArgSize;
WB_CIPHER DecryptArgs;
ULONG KeySize;
WB_KEY DecryptKey;
} SP_ENCRYPTED_DATA, * PSP_ENCRYPTED_DATA;
La structure d'entrée déchiffrée contient la quantité de paramètres relatifs au type d'informations de politique et un en-tête qui spécifie le type d'informations ainsi que les arguments nécessaires au chiffrement WarBird pour chiffrer les données.
typedef struct _SP_DECRYPTED_HEADER
{
ULONG PolicyTypeSize;
SYSTEM_POLICY_CLASS PolicyType;
ULONG EncyptArgSize;
WB_CIPHER EncryptArgs;
ULONG KeySize;
WB_KEY EncryptKey;
SP_BODY Body;
} SP_DECRYPTED_HEADER;
typedef struct _SP_DECRYPTED_DATA
{
ULONG ParameterCount;
ULONG DecryptedSize;
SP_DECRYPTED_HEADER HeaderData;
ULONG a;
USHORT b;
} SP_DECRYPTED_DATA;
Une fois le chiffrement WarBird et les clés nécessaires pour chiffrer et déchiffrer les données préparés, les données sont chiffrées et l'exécution est transmise à NtQuerySystemInformation
. La table de commutation de classe d'informations enverra finalement les données à SPCall2ServerInternal
, où elle déchiffrera et vérifiera les données et invoquera l'une des routines de licence internes. Quelques-unes des classes de politiques inversées sont présentées :
typedef enum _SYSTEM_POLICY_CLASS
{
QueryPolicy,
UpdatePolicies,
AuthenticateCaller,
WaitForDisplayWindow = 5 ,
FileUsnQuery = 22 ,
FileIntegrityUpdate,
FileIntegrityQuery,
UpdateLicense = 100 ,
RemoveLicense,
NotImplemented,
CreateLicenseEfsHeader,
LicenseEfsHeaderContainsFek,
GetLicenseChallange,
GetBaseContentKeyFromLicense,
GetBaseContentKeyFromKeyID,
IsAppLicensed = 109 ,
DumpLicenseGroup,
Clear,
ClepSign,
ClepKdf,
UpdateOsLicenseBlob = 204 ,
CheckLicense,
GetCurrentHardwareID,
CreateLicenseKeyIDEfsHeader,
GetAppPolicyValue,
QueryCachedOptionalInfo,
AcRequest,
AcHmac,
UpdateImdsResponse
} SYSTEM_POLICY_CLASS;
Quelques-unes des routines de licence seront ensuite envoyées à une fonction située dans une table globale, nt!g_kernelCallbacks
. Cette table de fonctions globale contient des pointeurs de fonction à l'intérieur de clipsp.sys
, qui gère la politique du système de licence client. Lors de l'initialisation des données de licence, le noyau configurera d'abord l'état de la licence dans un silo de serveur global ( PspHostSiloGlobals->ExpLicenseState
) et chargera les valeurs de licence à partir du registre sous ProductOptions
. Il appellera ensuite ExInitLicenseData
qui mettra à jour les données de licence et configurera la protection des données du noyau. La routine finira par appeler ClipInitHandles
, qui initialise les globales utilisées pour les rappels de licence client avec g_kernelCallbacks
. Le noyau ne configure pas réellement la table de rappel globale du noyau dans ClipInitHandles
, mais il transmettra à la place la table à ClipSpInitialize
située dans clipsp.sys
.
L'image de stratégie système de licence client ( clipsp
) est responsable de la gestion des éléments internes de la fonctionnalité de stratégie système dans le noyau. En tant que tel, il est masqué par Microsoft WarBird pour empêcher l’ingénierie inverse. L'image contient plusieurs sections à haute entropie ( PAGEwx1
etc.) et des noms qui indiquent qu'elle sera décompressée et exécutée pendant l'exécution.
Clipsp fera appel au Warbird Runtime pour décompresser le code avant l'exécution et le reconditionner ensuite. Les fonctions alloueront plusieurs listes de descripteurs de mémoire (MDL) pour remapper les pages physiques vers une adresse virtuelle rwx dans l'espace système. Le dumping de l'image au moment de l'exécution ne sera pas fiable car les sections sont reconditionnées après l'exécution et seules celles nécessaires à l'exécution seront décompressées. Une méthode simple pour décompresser automatiquement les sections consiste à émuler les routines de décryptage avec un cadre d'émulation binaire tel que Qiling. J'ai écrit un simple script de décompression en Python qui émulera diverses API du noyau et videra la section décompressée une fois le MDL libéré.
Une analyse plus approfondie peut être effectuée après avoir remplacé les sections compressées par le code non compressé. ClipSpInitialize
fera appel à SpInitialize
pour remplir g_kernelCallbacks
, configurer les clés de registre et initialiser les fournisseurs CNG et les clés cryptographiques.
Le sous-programme SpInitializeDeviceExtension
vérifiera d'abord les droits d'accès à une clé de registre spéciale située dans \Registry\Machine\System\CurrentControlSet\Control\{7746D80F-97E0-4E26-9543-26B41FC22F79}
réservée aux droits numériques. L'accès à la clé de registre spécifique est destiné uniquement à une utilisation sous licence et les tentatives d'y accéder à partir d'un processus non privilégié entraîneront ACCESS_DENIED
. De plus, il accédera à plusieurs sous-clés sous la même clé, notamment {A25AE4F2-1B96-4CED-8007-AA30E9B1A218}
, {D73E01AC-F5A0-4D80-928B-33C1920C38BA}
, {59AEE675-B203-4D61-9A1F-04518A20F359}
, {FB9F5B62-B48B-45F5-8586-E514958C92E2}
et {221601AB-48C7-4970-B0EC-96E66F578407}
.
Une ingénierie inverse plus poussée des rappels individuels nécessite une ingénierie inverse de la structure _EXP_LICENSE_STATE
dans _ESERVERSILO_GLOBALS
.