NtQuerySystemInformation
システム コールのSystemPolicyInformation
クラスから公開される、Windows カーネルのクライアント ライセンス システムに関する調査。
クライアント ライセンスと直接対話する主要なユーザーモード サービスには、 clipc.dll
(クライアント ライセンス プラットフォーム クライアント) とclipsvc.dll
(クライアント ライセンス サービス) の 2 つがあります。クライアント ライセンスのクエリを処理するカーネル イメージは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 ビット復号キーを含む 3 つのデータ ブロックで構成されます。復号化された入力データには、ポリシー情報のタイプ、引数の数、データを暗号化するための暗号引数とキーが含まれています。復号化されたデータの XOR チェックサムが暗号化されたデータに追加され、復号化後に検証されます。復号化暗号ブロックは、先頭に暗号サブルーチンの引数が配置され、最後に引数が配置される形式になっています。カーネルは 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
にディスパッチし、そこでデータを復号化して検証し、内部ライセンス ルーチンの 1 つを呼び出します。逆にされたポリシー クラスのいくつかが示されています。
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 ランタイムを呼び出します。この関数は、物理ページをシステム空間の rwx 仮想アドレスに再マップするために、いくつかのメモリ記述子リスト (MDL) を割り当てます。セクションは実行後に再パックされ、実行に必要なセクションのみがアンパックされるため、実行時にイメージをダンプすることは信頼できません。セクションを自動的に解凍する簡単な方法は、Qiling などのバイナリ エミュレーション フレームワークを使用して復号化ルーチンをエミュレートすることです。さまざまなカーネル API をエミュレートし、MDL が解放されるとアンパックされたセクションをダンプする単純なアンパッカー スクリプトを Python で作成しました。
パックされたセクションをアンパックされたコードに置き換えた後、さらに分析を行うことができます。 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
構造体のリバース エンジニアリングが必要です。