Investigación sobre el sistema de licencias de cliente en el kernel de Windows expuesto desde la clase SystemPolicyInformation
en la llamada al sistema NtQuerySystemInformation
.
Hay dos servicios principales de modo de usuario que interactúan directamente con las licencias del cliente: clipc.dll
(Cliente de plataforma de licencias de cliente) y clipsvc.dll
(Servicio de licencia de cliente). La imagen del kernel que maneja las consultas de licencia del cliente es clipsp.sys
(Política del sistema de licencia del cliente). Como la atención se centra en los aspectos internos de las rutinas de licencia en el kernel, no se mencionará mucho sobre los servicios en modo de usuario.
El cliente inicia el servicio de licencia a través del administrador de servicios y se comunica con el servicio mediante llamadas a procedimientos remotos (RPC). El servicio registra varios controladores que se utilizan en la API de licencias de software.
Los controladores que deben interactuar con la información de licencia del kernel invocarán NtQuerySystemInformation
con la clase de información SystemPolicyInformation
. Geoff Chappell ha documentado la estructura de clases SystemPolicyInformation
para la transferencia de datos. La estructura se muestra a continuación:
typedef struct _SYSTEM_POLICY_INFORMATION
{
PVOID InputData;
PVOID OutputData;
ULONG InputSize;
ULONG OutputSize;
ULONG Version;
NTSTATUS Status;
} SYSTEM_POLICY_INFORMATION, * PSYSTEM_POLICY_INFORMATION;
La página de referencia en MSDN para la clase de información ofrece una estructura aún más incompleta y sugiere utilizar la API SL de nivel superior. Como tal, las estructuras internas utilizadas por el kernel no están documentadas. Cada estructura interna documentada en mi investigación ha sido sometida a ingeniería inversa y nombrada de acuerdo con su uso inferido y es posible que no refleje con precisión el uso interno real.
Una breve ingeniería inversa de los controladores de licencia ClipSVC revela que las estructuras de entrada y salida se cifran y descifran utilizando un cifrado implementado por el ofuscador interno 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 ha sido investigado en años anteriores y expone varios pases de ofuscación diferentes, incluida la ofuscación de máquinas virtuales, el empaquetado de código e incluso la funcionalidad integrada en el kernel de Windows para descifrar y ejecutar cargas útiles firmadas en el montón utilizando un cifrado feistel al exponer una clase especial de información del sistema. : SystemControlFlowTransition
.
La estructura interna analizada por el kernel consta de tres bloques de datos que contienen los datos cifrados, los argumentos de descifrado para el cifrado WarBird y una clave de descifrado de 64 bits. Los datos de entrada descifrados contienen el tipo de información de la política, el recuento de argumentos y los argumentos de cifrado y la clave para cifrar los datos. Se agrega una suma de verificación XOR para los datos descifrados a los datos cifrados y se verifica después del descifrado. El bloque de cifrado de descifrado está formateado con argumentos para subrutinas de cifrado ubicados en la parte superior y argumentos en la parte inferior. El kernel pasará los parámetros en orden inverso durante 16 iteraciones. Las claves y los argumentos de cifrado están codificados según la clase de política.
// +-------------------+ +-------------------+
// | 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 estructura de entrada descifrada contiene la cantidad de parámetros relativos al tipo de información de la política y un encabezado que especifica el tipo de información junto con los argumentos necesarios para que el cifrado WarBird cifre los datos.
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;
Una vez que se preparan el cifrado WarBird y las claves necesarias para cifrar y descifrar los datos, los datos se cifran y la ejecución se pasa a NtQuerySystemInformation
. La tabla de cambio de clase de información eventualmente enviará los datos a SPCall2ServerInternal
, donde descifrará y verificará los datos e invocará una de las rutinas de licencia interna. Se muestran algunas de las clases de políticas invertidas:
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;
Algunas de las rutinas de licencia se enviarán a una función ubicada en una tabla global, nt!g_kernelCallbacks
. Esta tabla de funciones globales contiene punteros de funciones dentro de clipsp.sys
, que maneja la política del sistema de licencias del cliente. Durante la inicialización de los datos de la licencia, el kernel primero configurará el estado de la licencia en un silo de servidor global ( PspHostSiloGlobals->ExpLicenseState
) y cargará los valores de la licencia desde el registro en ProductOptions
. Luego llamará ExInitLicenseData
, que actualizará los datos de la licencia y configurará la protección de datos del kernel. La rutina eventualmente llamará a ClipInitHandles
, que inicializa los globales utilizados para las devoluciones de llamadas de licencias de clientes junto con g_kernelCallbacks
. En realidad, el kernel no configura la tabla de devolución de llamada global del kernel en ClipInitHandles
, sino que pasará la tabla a ClipSpInitialize
ubicado en clipsp.sys
.
La imagen de la política del sistema de licencias del cliente ( clipsp
) es responsable de manejar los aspectos internos de la funcionalidad de la política del sistema en el kernel. Como tal, está ofuscado con Microsoft WarBird para evitar la ingeniería inversa. La imagen contiene varias secciones con alta entropía ( PAGEwx1
, etc.) y nombres que indican que será descomprimida y ejecutada durante el tiempo de ejecución.
Clipsp solicitará a Warbird Runtime que descomprima el código antes de la ejecución y lo vuelva a empaquetar después. Las funciones asignarán varias listas de descriptores de memoria (MDL) para reasignar las páginas físicas a una dirección virtual rwx en el espacio del sistema. Volcar la imagen en tiempo de ejecución no será confiable ya que las secciones se vuelven a empaquetar después de la ejecución y solo se descomprimirán las necesarias para la ejecución. Un método sencillo para descomprimir automáticamente las secciones es emular las rutinas de descifrado con un marco de emulación binaria como Qiling. He escrito un script de desempaquetado simple en Python que emulará varias API del kernel y volcará la sección desempaquetada una vez que se libere el MDL.
Se pueden realizar más análisis después de reemplazar las secciones empaquetadas con el código desempaquetado. ClipSpInitialize
llamará a SpInitialize
para completar g_kernelCallbacks
, configurar claves de registro e inicializar proveedores de CNG y claves criptográficas.
La subrutina SpInitializeDeviceExtension
primero verificará los derechos de acceso a una clave de registro especial ubicada en \Registry\Machine\System\CurrentControlSet\Control\{7746D80F-97E0-4E26-9543-26B41FC22F79}
reservada para derechos digitales. El acceso a la clave de registro específica está destinado únicamente al uso de licencia y los intentos de acceder a ella desde un proceso sin privilegios darán lugar a ACCESS_DENIED
. Además, accederá a varias subclaves bajo la misma clave, incluidas {A25AE4F2-1B96-4CED-8007-AA30E9B1A218}
, {D73E01AC-F5A0-4D80-928B-33C1920C38BA}
, {59AEE675-B203-4D61-9A1F-04518A20F359}
, {FB9F5B62-B48B-45F5-8586-E514958C92E2}
y {221601AB-48C7-4970-B0EC-96E66F578407}
.
Una mayor ingeniería inversa de las devoluciones de llamada individuales requiere ingeniería inversa de la estructura _EXP_LICENSE_STATE
en _ESERVERSILO_GLOBALS
.