研究从NtQuerySystemInformation
系统调用中的SystemPolicyInformation
类公开的 Windows 内核中的客户端许可系统。
有两个主要的用户模式服务直接与客户端许可交互: clipc.dll
(客户端许可平台客户端)和clipsvc.dll
(客户端许可服务)。处理客户端许可证查询的内核映像是clipsp.sys
(客户端许可证系统策略)。由于重点是内核中许可例程的内部结构,因此不会过多提及用户模式服务。
客户端通过服务管理器启动许可证服务,并通过远程过程调用(RPC)与服务进行通信。该服务注册了几个在软件许可 API 中使用的处理程序。
必须与内核许可信息交互的处理程序将使用SystemPolicyInformation
信息类调用NtQuerySystemInformation
。用于数据传输的SystemPolicyInformation
类结构已由 Geoff Chappell 记录。结构如下图所示:
typedef struct _SYSTEM_POLICY_INFORMATION
{
PVOID InputData;
PVOID OutputData;
ULONG InputSize;
ULONG OutputSize;
ULONG Version;
NTSTATUS Status;
} SYSTEM_POLICY_INFORMATION, * PSYSTEM_POLICY_INFORMATION;
MSDN 中信息类的参考页面提供了更不完整的结构,并建议使用更高级别的 SL API。因此,内核使用的内部结构没有文档记录。我的研究中记录的每个内部结构都经过逆向工程,并根据其推断的用途进行命名,可能无法准确反映实际的内部用途。
ClipSVC 许可证处理程序的简短逆向工程表明,输入和输出结构是使用 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 已在前几年进行了研究,并公开了各种不同的混淆过程,包括虚拟机混淆、代码打包,甚至集成了 Windows 内核的功能,通过公开特殊的系统信息类,使用 Feistel 密码在堆上解密和执行签名的有效负载: SystemControlFlowTransition
。
内核解析的内部结构由三个数据块组成,其中包含加密数据、WarBird 密码的解密参数和 64 位解密密钥。解密的输入数据包含策略信息类型、参数计数以及加密数据的密码参数和密钥。解密数据的异或校验和被附加到加密数据上,并在解密后进行验证。解密密码块的格式为位于顶部的密码子例程的参数和位于底部的参数。内核将以相反的顺序传递参数,进行 16 次迭代。密钥和密码参数根据策略类进行硬编码。
// +-------------------+ +-------------------+
// | 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;
解密的输入结构包含与策略信息类型相关的参数数量和指定信息类型的标头以及 WarBird 密码加密数据所需的参数。
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;
一旦准备好加密和解密数据所需的 WarBird 密码和密钥,数据就会被加密,并将执行传递到NtQuerySystemInformation
。信息类切换表最终会将数据分派到SPCall2ServerInternal
,在那里它将解密和验证数据并调用内部许可证例程之一。显示了一些反向策略类别:
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;
一些许可例程将进一步分派到位于全局表nt!g_kernelCallbacks
中的函数。该全局函数表包含clipsp.sys
内部的函数指针,用于处理客户端许可证系统策略。在许可证数据初始化期间,内核将首先在全局服务器孤岛( PspHostSiloGlobals->ExpLicenseState
)中设置许可证状态,并从ProductOptions
下的注册表加载许可证值。然后它将调用ExInitLicenseData
,它将更新许可证数据并设置内核数据保护。该例程最终将调用ClipInitHandles
,它初始化用于客户端许可回调的全局变量以及g_kernelCallbacks
。内核实际上并没有在ClipInitHandles
中设置全局内核回调表,而是将该表传递给位于clipsp.sys
中的ClipSpInitialize
。
客户端许可系统策略映像 ( clipsp
) 负责处理内核中系统策略功能的内部结构。因此,它与 Microsoft WarBird 混淆以防止逆向工程。该图像包含几个具有高熵的部分( PAGEwx1
等)以及指示它将在运行时解压和执行的名称。
Clipsp 将调用 Warbird 运行时在执行前解压代码并在执行后重新打包。这些函数将分配多个内存描述符列表 (MDL),以将物理页重新映射到系统空间中的 rwx 虚拟地址。在运行时转储图像并不可靠,因为这些部分在执行后会重新打包,并且只有执行所需的部分才会被解包。自动解包这些部分的一个简单方法是使用二进制模拟框架(例如 Qiling)来模拟解密例程。我用 Python 编写了一个简单的解包器脚本,它将模拟各种内核 API,并在释放 MDL 后转储解包部分。
将打包的部分替换为未打包的代码后,可以进行进一步的分析。 ClipSpInitialize
将调用SpInitialize
来填充g_kernelCallbacks
、设置注册表项并初始化 CNG 提供程序和加密密钥。
SpInitializeDeviceExtension
子例程将首先验证对位于\Registry\Machine\System\CurrentControlSet\Control\{7746D80F-97E0-4E26-9543-26B41FC22F79}
为数字权利保留的特殊注册表项的访问权限。对特定注册表项的访问仅用于许可证使用,尝试从非特权进程访问它将导致ACCESS_DENIED
。此外,它将访问同一密钥下的多个子密钥,包括{A25AE4F2-1B96-4CED-8007-AA30E9B1A218}
、 {D73E01AC-F5A0-4D80-928B-33C1920C38BA}
、 {59AEE675-B203-4D61-9A1F-04518A20F359}
、 {FB9F5B62-B48B-45F5-8586-E514958C92E2}
和{221601AB-48C7-4970-B0EC-96E66F578407}
。
对各个回调的进一步逆向工程需要对_ESERVERSILO_GLOBALS
中的_EXP_LICENSE_STATE
结构进行逆向工程。