การวิจัยเกี่ยวกับระบบสิทธิ์การใช้งานไคลเอ็นต์ในเคอร์เนล Windows ที่เปิดเผยจากคลาส SystemPolicyInformation
ในการเรียกระบบ NtQuerySystemInformation
มีบริการโหมดผู้ใช้หลักสองบริการที่โต้ตอบโดยตรงกับการให้สิทธิ์การใช้งานไคลเอนต์: clipc.dll
(ไคลเอนต์แพลตฟอร์มลิขสิทธิ์ไคลเอนต์) และ clipsvc.dll
(บริการสิทธิ์การใช้งานไคลเอนต์) อิมเมจเคอร์เนลที่จัดการแบบสอบถามสิทธิ์การใช้งานไคลเอ็นต์คือ clipsp.sys
(นโยบายระบบสิทธิ์การใช้งานไคลเอ็นต์) เนื่องจากการมุ่งเน้นไปที่ภายในของรูทีนการออกใบอนุญาตในเคอร์เนล จึงไม่มีการกล่าวถึงบริการโหมดผู้ใช้มากนัก
ไคลเอนต์เริ่มบริการสิทธิ์การใช้งานผ่านผู้จัดการบริการและสื่อสารกับบริการผ่านการเรียกขั้นตอนระยะไกล (RPC) บริการจะลงทะเบียนตัวจัดการหลายตัวที่ใช้ใน Software Licensing API
ตัวจัดการที่ต้องเชื่อมต่อกับข้อมูลสิทธิ์การใช้งานเคอร์เนลจะเรียกใช้ NtQuerySystemInformation
กับคลาสข้อมูล SystemPolicyInformation
โครงสร้างคลาส 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 เปิดเผยว่าโครงสร้างอินพุตและเอาต์พุตได้รับการเข้ารหัสและถอดรหัสโดยใช้รหัสที่นำมาใช้โดย WarBird ภายในของ Microsoft
// 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 ได้รับการวิจัยในปีก่อน ๆ และเปิดเผยรหัสผ่าน obfuscation ที่แตกต่างกันมากมาย รวมถึงการ obfuscation ของเครื่องเสมือน การบรรจุโค้ด และแม้แต่ฟังก์ชันการทำงานที่รวมเข้ากับเคอร์เนลของ 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
ซึ่งจะอัปเดตข้อมูลใบอนุญาตและตั้งค่า Kernel Data Protection ในที่สุดรูทีนจะเรียก ClipInitHandles
ซึ่งเริ่มต้น globals ที่ใช้สำหรับการโทรกลับสิทธิ์การใช้งานไคลเอนต์พร้อมกับ g_kernelCallbacks
เคอร์เนลไม่ได้ตั้งค่าตารางการเรียกกลับเคอร์เนลส่วนกลางใน ClipInitHandles
แต่จะส่งตารางไปที่ ClipSpInitialize
ที่อยู่ใน clipsp.sys
แทน
อิมเมจนโยบายระบบการให้สิทธิ์ไคลเอ็นต์ ( clipsp
) มีหน้าที่รับผิดชอบในการจัดการการทำงานภายในของนโยบายระบบในเคอร์เนล ด้วยเหตุนี้ Microsoft WarBird จึงสับสนเพื่อป้องกันการทำวิศวกรรมย้อนกลับ รูปภาพประกอบด้วยหลายส่วนที่มีเอนโทรปีสูง ( PAGEwx1
ฯลฯ) และชื่อที่ระบุว่าจะถูกคลายแพ็กและดำเนินการระหว่างรันไทม์
Clipsp จะเรียกใช้ Warbird Runtime เพื่อแกะโค้ดก่อนดำเนินการและทำการแพ็คใหม่ในภายหลัง ฟังก์ชันจะจัดสรรรายการตัวอธิบายหน่วยความจำ (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}
วิศวกรรมย้อนกลับเพิ่มเติมของการเรียกกลับแต่ละครั้งจำเป็นต้องมีวิศวกรรมย้อนกลับของโครงสร้าง _EXP_LICENSE_STATE
ใน _ESERVERSILO_GLOBALS