Pesquisa sobre o sistema de licenciamento de cliente no kernel do Windows exposto pela classe SystemPolicyInformation
na chamada de sistema NtQuerySystemInformation
.
Existem dois serviços principais de modo de usuário que interagem diretamente com o licenciamento do cliente: clipc.dll
(cliente da plataforma de licenciamento de cliente) e clipsvc.dll
(serviço de licença de cliente). A imagem do kernel que lida com consultas de licença de cliente é clipsp.sys
(Política de Sistema de Licença de Cliente). Como o foco está nos aspectos internos das rotinas de licenciamento no kernel, pouco será mencionado sobre os serviços de modo de usuário.
O cliente inicia o serviço de licença por meio do gerenciador de serviços e se comunica com o serviço por meio de chamadas de procedimento remoto (RPC). O serviço registra vários manipuladores que são usados na API de licenciamento de software.
Os manipuladores que devem interagir com as informações de licenciamento do kernel invocarão NtQuerySystemInformation
com a classe de informações SystemPolicyInformation
. A estrutura da classe SystemPolicyInformation
para transferência de dados foi documentada por Geoff Chappell. A estrutura é mostrada abaixo:
typedef struct _SYSTEM_POLICY_INFORMATION
{
PVOID InputData;
PVOID OutputData;
ULONG InputSize;
ULONG OutputSize;
ULONG Version;
NTSTATUS Status;
} SYSTEM_POLICY_INFORMATION, * PSYSTEM_POLICY_INFORMATION;
A página de referência no MSDN para a classe de informações oferece uma estrutura ainda mais incompleta e sugere o uso da API SL de nível superior. Como tal, as estruturas internas usadas pelo kernel não estão documentadas. Cada estrutura interna documentada em minha pesquisa foi submetida a engenharia reversa e nomeada de acordo com seu uso inferido e pode não refletir com precisão o uso interno real.
Uma breve engenharia reversa dos manipuladores de licença ClipSVC revela que as estruturas de entrada e saída são criptografadas e descriptografadas usando uma cifra implementada pelo ofuscador interno da 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;
O Microsoft WarBird foi pesquisado em anos anteriores e expõe vários passes de ofuscação diferentes, incluindo ofuscação de máquina virtual, empacotamento de código e até mesmo funcionalidade integrada ao kernel do Windows para descriptografar e executar cargas assinadas no heap usando uma cifra Feistel, expondo uma classe especial de informações do sistema : SystemControlFlowTransition
.
A estrutura interna analisada pelo kernel consiste em três blocos de dados contendo os dados criptografados, os argumentos de descriptografia para a cifra WarBird e uma chave de descriptografia de 64 bits. Os dados de entrada descriptografados contêm o tipo de informação da política, a contagem de argumentos e os argumentos de cifra e a chave para criptografar os dados. Uma soma de verificação XOR para os dados descriptografados é anexada aos dados criptografados e verificada após a descriptografia. O bloco de criptografia de descriptografia é formatado com argumentos para sub-rotinas de criptografia posicionados na parte superior e argumentos na parte inferior. O kernel passará os parâmetros na ordem inversa por 16 iterações. As chaves e os argumentos de cifra são codificados dependendo da classe 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;
A estrutura de entrada descriptografada contém a quantidade de parâmetros relativos ao tipo de informação da política e um cabeçalho que especifica o tipo de informação junto com os argumentos necessários para a cifra WarBird criptografar os dados.
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;
Depois que a cifra WarBird e as chaves necessárias para criptografar e descriptografar os dados forem preparadas, os dados serão criptografados e a execução será passada para NtQuerySystemInformation
. A tabela de troca de classe de informações eventualmente despachará os dados para SPCall2ServerInternal
, onde irá descriptografar e verificar os dados e invocar uma das rotinas de licença internas. Algumas das classes de política invertidas são mostradas:
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;
Algumas das rotinas de licenciamento serão despachadas para uma função localizada em uma tabela global, nt!g_kernelCallbacks
. Esta tabela de funções globais contém ponteiros de função dentro de clipsp.sys
, que trata da política do sistema de licença do cliente. Durante a inicialização dos dados de licença, o kernel primeiro configurará o estado da licença em um silo de servidor global ( PspHostSiloGlobals->ExpLicenseState
) e carregará os valores de licença do registro em ProductOptions
. Em seguida, ele chamará ExInitLicenseData
, que atualizará os dados da licença e configurará o Kernel Data Protection. A rotina eventualmente chamará ClipInitHandles
, que inicializa globais usados para retornos de chamada de licenciamento de cliente junto com g_kernelCallbacks
. Na verdade, o kernel não configura a tabela de retorno de chamada do kernel global em ClipInitHandles
, mas em vez disso passará a tabela para ClipSpInitialize
localizado em clipsp.sys
.
A imagem da política do sistema de licenciamento do cliente ( clipsp
) é responsável por lidar com os aspectos internos da funcionalidade da política do sistema no kernel. Como tal, é ofuscado pelo Microsoft WarBird para evitar engenharia reversa. A imagem contém diversas seções com alta entropia ( PAGEwx1
etc.) e nomes que indicam que ela será descompactada e executada durante a execução.
O Clipsp chamará o Warbird Runtime para descompactar o código antes da execução e reembalá-lo depois. As funções alocarão várias listas de descritores de memória (MDLs) para remapear as páginas físicas para um endereço virtual rwx no espaço do sistema. O despejo da imagem em tempo de execução não será confiável, pois as seções serão reembaladas após a execução e apenas aquelas necessárias para a execução serão descompactadas. Um método simples para descompactar automaticamente as seções é emular as rotinas de descriptografia com uma estrutura de emulação binária como o Qiling. Eu escrevi um script de descompactação simples em Python que irá emular várias APIs do kernel e despejar a seção descompactada assim que o MDL for liberado.
Análises adicionais podem ser feitas após substituir as seções compactadas pelo código descompactado. ClipSpInitialize
chamará SpInitialize
para preencher g_kernelCallbacks
, configurar chaves de registro e inicializar provedores de CNG e chaves criptográficas.
A sub-rotina SpInitializeDeviceExtension
primeiro verificará os direitos de acesso a uma chave de registro especial localizada em \Registry\Machine\System\CurrentControlSet\Control\{7746D80F-97E0-4E26-9543-26B41FC22F79}
reservada para direitos digitais. O acesso à chave de registro específica destina-se apenas ao uso de licença e as tentativas de acessá-la a partir de um processo sem privilégios resultarão em ACCESS_DENIED
. Além disso, ele acessará várias subchaves na mesma chave, incluindo {A25AE4F2-1B96-4CED-8007-AA30E9B1A218}
, {D73E01AC-F5A0-4D80-928B-33C1920C38BA}
, {59AEE675-B203-4D61-9A1F-04518A20F359}
, {FB9F5B62-B48B-45F5-8586-E514958C92E2}
e {221601AB-48C7-4970-B0EC-96E66F578407}
.
A engenharia reversa adicional dos retornos de chamada individuais requer engenharia reversa da estrutura _EXP_LICENSE_STATE
em _ESERVERSILO_GLOBALS
.