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비트 암호 해독 키를 포함하는 세 개의 데이터 블록으로 구성됩니다. 해독된 입력 데이터에는 정책 정보 유형, 인수 개수, 데이터를 암호화하기 위한 암호 인수 및 키가 포함됩니다. 해독된 데이터에 대한 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
로 전달하여 데이터를 해독 및 확인하고 내부 라이센스 루틴 중 하나를 호출합니다. 역방향 정책 클래스 중 몇 가지가 표시됩니다.
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
호출합니다. 루틴은 결국 g_kernelCallbacks
와 함께 클라이언트 라이센스 콜백에 사용되는 전역을 초기화하는 ClipInitHandles
호출합니다. 커널은 실제로 ClipInitHandles
에 전역 커널 콜백 테이블을 설정하지 않지만 대신 테이블을 clipsp.sys
에 있는 ClipSpInitialize
에 전달합니다.
클라이언트 라이센스 시스템 정책 이미지( clipsp
)는 커널에서 시스템 정책 기능의 내부를 처리하는 역할을 합니다. 따라서 리버스 엔지니어링을 방지하기 위해 Microsoft WarBird로 난독화됩니다. 이미지에는 엔트로피가 높은 여러 섹션( PAGEwx1
등)과 런타임 중에 압축이 풀려 실행될 것임을 나타내는 이름이 포함되어 있습니다.
Clipsp는 Warbird Runtime을 호출하여 실행 전에 코드 압축을 풀고 나중에 다시 압축합니다. 이 함수는 여러 MDL(메모리 설명자 목록)을 할당하여 물리적 페이지를 시스템 공간의 rwx 가상 주소로 다시 매핑합니다. 실행 후 섹션이 다시 압축되고 실행에 필요한 섹션만 압축 해제되므로 런타임에 이미지를 덤프하는 것은 신뢰할 수 없습니다. 섹션의 압축을 자동으로 풀기 위한 간단한 방법은 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
구조를 리버스 엔지니어링해야 합니다.