/*-- Copyright (C) Microsoft Corporation, 1999 --*/ // @@BEGIN_DDKSPLIT /*-- Module Name: sec.c Abstract: !!! THIS IS SENSITIVE INFORMATION !!! THIS CODE MUST NEVER BE INCLUDED IN ANY EXTERNAL DOCUMENTATION These functions MUST be static to prevent symbols when we ship Environment: kernel mode only Revision History: --*/ /* KEY1 == \Registry\Machine\Software\Microsoft\ DPID == \Registry\Machine\Software\Microsoft\ Windows NT\CurrentVersion\DigitalProductId [REG_BINARY] KEY2 == KEY1 + DPID Hash PHSH == DPID Hash DHSH == Drive Hash based off vendor, product, revision, and serial no. UVAL == obfuscated value containing both current region and reset count Overview: KEY1 and DPID must both exist. furthermore, it is a given that the DPID is unique to a machine and changing it is catastrophic. Based upon these presumptions, we use the DPID to create a semi-unique key under KEY1 that is based off the DPID (KEY2). KEY2 will store values for each DVD RPC Phase 1 drive. It is also a given that both the region AND reset count will change each time the key is written. This allows the obfuscation method to rely on either the reset or the region, but does not require both. Each byte should rely on one of the above, in order to prevent any large sequences of bytes from staying the same between changes. Each value under KEY2 will have a one-to-one correlation to a specific TYPE of drive (UVAL). Identical drives will share regions and region reset counts. This is a "better" solution than sharing region and reset counts for all devices, which was the only other choice. OEMs must be made aware of this. This is a good reason to install RPC Phase 2 drives into machines. The UVAL is read by CdromGetRpc0Settings(). If the read results in invalid data, we will mark the device as VIOLATING the license agreements. The UVAL name is based upon the DHSH as follows: Take the DHSH and copy it as legal characters for the registry by OR'ing with the value 0x20 (all characters higher than 20 are legal? -- verify with JVERT). This also has the benefit of being a LOSSY method, but with a FIXED string length. The data within UVAL is REG_QWORD. The data breakdown can be found in the functions SecureDvdEncodeSettings() and SecureDvdDecodeSettings() NOTE: One main difficulty still exists in determining the difference between the above key not existing due to user deletion vs. the above key not existing due to first install of this drive. OPTIONAL: It is highly preferred to have KEY3 seeded in the system hive. This will prevent the casual deletion of the KEY3 tree to reset all the region counts to max. It is unknown if this is simple at this time, but allows option 2 (below) to change to deletion, which may be a better option. OPTIONAL: save another key (UKEY), which, if it exists, means this machine should NEVER be allowed to work again. this will force a reinstall, and reduce the effectiveness of a brute-force attack to an unmodified driver unless they realize this key is being set. this will also allow a method to determine if a user deleted KEY2. PSS can know about this magic key. cdrom should log an EVENTLOG saying that the CSS license agreement has been breached. this key should never be set for any error conditions when reading the key. FunctionalFlow: ReadDvdRegionAndResetCount() [O] if (SecureDvdLicenseBreachDetected()) { [O] LogLicenseError(); [O] return; [O] } if (!NT_SUCCESS(SecureDvdGetRegKeyHandle(h)) && reason was DNE) { LogLicenseError(); return; } PHSH = SecureDvdGetProductHash(); if (PHSH == INVALID_HASH) { return; } DHSH = SecureDvdGetDriveHash(); if (DHSH == INVALID_HASH) { return; } if (!ReadValue( DriveKey, Data )) { INITIALIZE_DRIVE_DATA( Data ); } // // data exists, if it's incorrect, LogLicenseError() // if (!DecodeSettings( QWORD, DHSH, PHSH )) { LogLicenseError(); return; } // set region & count return; WriteDvdRegionAndResetCount() [O] if (SecureDvdLicenseBreachDetected()) { [O] return FALSE; [O] } if (!NT_SUCCESS(SecureDvdGetRegKeyHandle(h)) && reason was DNE) { return FALSE; } PHSH = SecureDvdGetProductHash(); if (PHSH == INVALID_HASH) { return FALSE; } DHSH = SecureDvdGetDriveHash(); if (DHSH == INVALID_HASH) { return FALSE; } QWORD = EncodeSettings( DHSH, PHSH, Region, Resets ); if (QWORD == INVALID_HASH) { return FALSE; } if (!WriteValue( DriveKey, Data )) { return FALSE; } return TRUE; */ // @@END_DDKSPLIT #include "sec.h" #include "sec.tmh" // @@BEGIN_DDKSPLIT // // the digital product id structure is defined // in \nt\private\windows\setup\pidgen\inc\pidgen.h // (this was as of 10/06/1999) // typedef struct { ULONG dwLength; SHORT wVersionMajor; SHORT wVersionMinor; UCHAR szPid2[24]; ULONG dwKeyIdx; UCHAR szSku[16]; UCHAR abCdKey[16]; ULONG dwCloneStatus; ULONG dwTime; ULONG dwRandom; ULONG dwLicenseType; ULONG adwLicenseData[2]; UCHAR szOemId[8]; ULONG dwBundleId; UCHAR aszHardwareIdStatic[8]; ULONG dwHardwareIdTypeStatic; ULONG dwBiosChecksumStatic; ULONG dwVolSerStatic; ULONG dwTotalRamStatic; ULONG dwVideoBiosChecksumStatic; UCHAR aszHardwareIdDynamic[8]; ULONG dwHardwareIdTypeDynamic; ULONG dwBiosChecksumDynamic; ULONG dwVolSerDynamic; ULONG dwTotalRamDynamic; ULONG dwVideoBiosChecksumDynamic; ULONG dwCrc32; } DIGITALPID, *PDIGITALPID; //////////////////////////////////////////////////////////////////////////////// // // These functions are not called externally. Make them static to make // debugging more difficult in the shipping versions. // //////////////////////////////////////////////////////////////////////////////// STATIC ULONG RotateULong( IN ULONG N, IN LONG BitsToRotate ) // validated for -64 through +64 { if (BitsToRotate < 0) { BitsToRotate = - BitsToRotate; // negate BitsToRotate %= 8*sizeof(ULONG); // less than bits BitsToRotate = 8*sizeof(ULONG) - BitsToRotate; // equivalent positive } else { BitsToRotate %= 8*sizeof(ULONG); // less than bits } return ((N << BitsToRotate) | (N >> ((8*sizeof(ULONG)) - BitsToRotate))); } STATIC ULONGLONG RotateULongLong( IN ULONGLONG N, IN LONG BitsToRotate ) // validated for -128 through +128 { if (BitsToRotate < 0) { BitsToRotate = - BitsToRotate; BitsToRotate %= 8*sizeof(ULONGLONG); BitsToRotate = 8*sizeof(ULONGLONG) - BitsToRotate; } else { BitsToRotate %= 8*sizeof(ULONGLONG); } return ((N << BitsToRotate) | (N >> ((8*sizeof(ULONGLONG)) - BitsToRotate))); } STATIC BOOLEAN SecureDvdRegionInvalid( IN UCHAR NegativeRegionMask ) // validated for all inputs { UCHAR positiveMask = ~NegativeRegionMask; if (positiveMask == 0) { ASSERT(!"This routine should never be called with the value 0xff"); return TRUE; } // // region non-zero, drop the lowest bit // (this is a cool hack, learned when implementing a fast // way to count the number of set bits in a variable.) // positiveMask = positiveMask & (positiveMask-1); // // if still non-zero, had more than one bit set // if (positiveMask) { TraceLog((CdromSecInfo, "DvdInvalidRegion: TRUE for many bits\n")); return TRUE; } return FALSE; } STATIC ULONGLONG SecureDvdGetDriveHash( IN PSTORAGE_DEVICE_DESCRIPTOR Descriptor ) // validated for all fields filled // validated for some fields NULL // validated for all fields NULL // validated for some fields invalid (too large?) // validated for all fields invalid (too large?) /* ** returns a ULONGLONG which is the HASH for a given DVD device. ** NOTE: because this does not check SCSI IDs, identical drives ** will share the same region and reset counts. */ { ULONGLONG checkSum = 0; ULONG characters = 0; LONG i; if (Descriptor->VendorIdOffset > 0x12345678) { TraceLog((CdromSecError, "DvdDriveHash: VendorIdOffset is too large (%x)\n", Descriptor->VendorIdOffset)); Descriptor->VendorIdOffset = 0; } if (Descriptor->ProductIdOffset > 0x12345678) { TraceLog((CdromSecError, "DvdDriveHash: ProductIdOffset is too large (%x)\n", Descriptor->ProductIdOffset)); Descriptor->ProductIdOffset = 0; } if (Descriptor->ProductRevisionOffset > 0x12345678) { TraceLog((CdromSecError, "DvdDriveHash: ProducetRevisionOffset is too " " large (%x)\n", Descriptor->ProductRevisionOffset)); Descriptor->ProductRevisionOffset = 0; } if (Descriptor->SerialNumberOffset > 0x12345678) { TraceLog((CdromSecError, "DvdDriveHash: SerialNumberOffset is too " "large (%x)\n", Descriptor->SerialNumberOffset)); Descriptor->SerialNumberOffset = 0; } if ((!Descriptor->VendorIdOffset ) && (!Descriptor->ProductIdOffset ) && (!Descriptor->ProductRevisionOffset) ) { TraceLog((CdromSecError, "DvdDriveHash: Invalid Descriptor at %p!\n", Descriptor)); return INVALID_HASH; } // // take one byte at a time, XOR together // should provide a semi-unique hash // for (i=0;i<4;i++) { PUCHAR string = (PUCHAR)Descriptor; ULONG offset = 0; switch(i) { case 0: // vendorId TraceLog((CdromSecInfo, "DvdDriveHash: Adding Vendor\n")); offset = Descriptor->VendorIdOffset; break; case 1: // productId TraceLog((CdromSecInfo, "DvdDriveHash: Adding Product\n")); offset = Descriptor->ProductIdOffset; break; case 2: // revision TraceLog((CdromSecInfo, "DvdDriveHash: Adding Revision\n")); offset = Descriptor->ProductRevisionOffset; break; case 3: // serialNumber TraceLog((CdromSecInfo, "DvdDriveHash: Adding SerialNumber\n")); offset = Descriptor->SerialNumberOffset; break; default: TraceLog((CdromSecError, "DvdDriveHash: TOO MANY LOOPS!!!\n")); offset = 0; break; } // // add the string to our checksum // if (offset != 0) { for (string += offset; *string; string++) { // // take each character, multiply it by a "random" // value. rotate the value. // ULONGLONG temp; if (*string == ' ') { // don't include spaces in the character count // nor in the hash continue; } // // dereference the value first! // temp = (ULONGLONG)(*string); // // guaranteed no overflow in UCHAR * ULONG in ULONGLONG // temp *= DVD_RANDOMIZER[ characters%DVD_RANDOMIZER_SIZE ]; // // this rotation is just to spread the values around // the 64 bits more evenly // temp = RotateULongLong(temp, 8*characters); // // increment number of characters used in checksum // (used to verify we have enough characters) // characters++; // // XOR it into the checksum // checkSum ^= temp; } // end of string if (checkSum == 0) { TraceLog((CdromSecInfo, "DvdDriveHash: zero checksum -- using " "random value\n")); checkSum ^= DVD_RANDOMIZER[ characters%DVD_RANDOMIZER_SIZE ]; characters++; } } // end of non-zero offset } // end of four strings (vendor, product, revision, serialNo) // // we have to use more than four characters // for this to be useful // if (characters <= 4) { TraceLog((CdromSecError, "DvdDriveHash: Too few useful characters (%x) " "for unique disk hash\n", characters)); return INVALID_HASH; } return checkSum; } // // static, not called externally // STATIC NTSTATUS SecureDvdEncodeSettings( IN ULONGLONG DpidHash, IN ULONGLONG DriveHash, OUT PULONGLONG Obfuscated, IN UCHAR RegionMask, IN UCHAR ResetCount ) // validated for all valid inputs. // validated for invalid inputs. { LARGE_INTEGER largeInteger = {0}; ULONGLONG set; LONG i; LONG rotate; UCHAR temp = 0; UCHAR random1; UCHAR random2; // // using the return from KeQueryTickCount() should give // semi-random data // KeQueryTickCount(&largeInteger); random2 = 0; for (i=0; i < sizeof(ULONGLONG); i++) { random2 ^= ((largeInteger.QuadPart >> (8*i)) & 0xff); } // set temp == sum of all 4-bit values // 16 in ULONGLONG, times max value of // 15 each is less than MAX_UCHAR for (i=0; i < 2*sizeof(ULONGLONG); i++) { temp += (UCHAR)( (DpidHash >> (4*i)) & 0xf ); } // // validate these settings here // if (DpidHash == INVALID_HASH) { TraceLog((CdromSecError, "DvdEncode: Invalid DigitalProductId Hash\n")); goto UserFailure; } if (DriveHash == INVALID_HASH) { TraceLog((CdromSecError, "DvdEncode: Invalid Drive Hash\n")); goto UserFailure; } if (RegionMask == 0xff) { TraceLog((CdromSecError, "DvdEncode: Shouldn't attempt to write " "mask of 0xff\n")); goto UserFailure; } if (SecureDvdRegionInvalid(RegionMask)) { TraceLog((CdromSecError, "DvdEncode: Invalid region\n")); goto LicenseViolation; } if (ResetCount >= 2) { TraceLog((CdromSecError, "DvdEncode: Too many reset counts\n")); goto LicenseViolation; } // // using the return from KeQueryTickCount() should give // semi-random data // KeQueryTickCount(&largeInteger); random1 = 0; for (i=0; i < sizeof(ULONGLONG); i++) { random1 ^= ((largeInteger.QuadPart >> (8*i)) & 0xff); } TraceLog((CdromSecInfo, "DvdEncode: Random1 = %x Random2 = %x\n", random1, random2)); // // they must all fit into UCHAR! they should, since each one is // individually a UCHAR, and only bitwise operations are being // performed on them. // // // the first cast to UCHAR prevents signed extension. // the second cast to ULONGLONG allows high bits preserved by '|' // set = (ULONGLONG)0; for (i=0; i < sizeof(ULONGLONG); i++) { set ^= (ULONGLONG)random2 << (8*i); } set ^= (ULONGLONG) ((ULONGLONG)((UCHAR)(random1 ^ temp)) << 8*7) | ((ULONGLONG)((UCHAR)(RegionMask ^ temp)) << 8*6) | ((ULONGLONG)((UCHAR)(ResetCount ^ RegionMask ^ random1)) << 8*5) | ((ULONGLONG)((UCHAR)(0)) << 8*4) | ((ULONGLONG)((UCHAR)(ResetCount ^ temp)) << 8*3) | ((ULONGLONG)((UCHAR)(ResetCount ^ ((DriveHash >> 13) & 0xff))) << 8*2) | ((ULONGLONG)((UCHAR)(random1)) << 8*1) | ((ULONGLONG)((UCHAR)(RegionMask ^ ((DriveHash >> 23) & 0xff))) << 8*0) ; TraceLog((CdromSecInfo, "DvdEncode: Pre-rotate: %016I64x temp = %x\n", set, temp)); // // rotate it a semi-random, non-multiple-of-eight bits // rotate = (LONG)((DpidHash & 0xb) + 1); // {15,14,10,9,7,5,2,1} TraceLog((CdromSecInfo, "DvdEncode: Rotating %x bits\n", rotate)); *Obfuscated = RotateULongLong(set, rotate); return STATUS_SUCCESS; UserFailure: *Obfuscated = INVALID_HASH; return STATUS_UNSUCCESSFUL; LicenseViolation: *Obfuscated = INVALID_HASH; return STATUS_LICENSE_VIOLATION; } STATIC NTSTATUS SecureDvdDecodeSettings( IN ULONGLONG DpidHash, IN ULONGLONG DriveHash, IN ULONGLONG Set, OUT PUCHAR RegionMask, OUT PUCHAR ResetCount ) // validated for many correct inputs, of all region/reset combinations // validated for many incorrect inputs. { UCHAR random; UCHAR region; UCHAR resets; UCHAR temp = 0; LONG i, rotate; // set temp == sum of all 4-bit values // 16 in ULONGLONG, times max value of // 15 each is less than MAX_UCHAR for (i=0; i < 2*sizeof(ULONGLONG); i++) { temp += (UCHAR)( (DpidHash >> (4*i)) & 0xf ); } rotate = (LONG)((DpidHash & 0xb) + 1); // {15,14,10,9,7,5,2,1} Set = RotateULongLong(Set, -rotate); TraceLog((CdromSecInfo, "DvdDecode: Post-rotate: %016I64x\n", Set)); random = (UCHAR)(Set >> 8*4); // random2 TraceLog((CdromSecInfo, "DvdDecode: Random2 = %x\n", random)); for (i = 0; i < sizeof(ULONGLONG); i++) { Set ^= (ULONGLONG)random << (8*i); } // // bytes 6,4,3,1 are taken 'as-is' // bytes 7,5,2,0 are verified // region = ((UCHAR)(Set >> 8*6)) ^ temp; resets = ((UCHAR)(Set >> 8*3)) ^ temp; random = ((UCHAR)(Set >> 8*1)); // make it random1 TraceLog((CdromSecInfo, "DvdDecode: Random1 = %x Region = %x Resets = %x\n", random, region, resets)); // verify the bits if (((UCHAR)(Set >> 8*7)) != (random ^ temp)) { TraceLog((CdromSecError, "DvdDecode: Invalid Byte 7\n")); goto ViolatedLicense; } random ^= (UCHAR)(Set >> 8*5); if (random != (resets ^ region)) { TraceLog((CdromSecError, "DvdDecode: Invalid Byte 5\n")); goto ViolatedLicense; } random = (UCHAR)(DriveHash >> 13); random ^= (UCHAR)(Set >> 8*2); if (random != resets) { TraceLog((CdromSecError, "DvdDecode: Invalid Byte 2\n")); goto ViolatedLicense; } random = (UCHAR)(DriveHash >> 23); random ^= (UCHAR)(Set >> 8*0); if (random != region) { TraceLog((CdromSecError, "DvdDecode: Invalid Byte 0\n")); goto ViolatedLicense; } if (SecureDvdRegionInvalid(region)) { TraceLog((CdromSecError, "DvdDecode: Region was invalid\n")); goto ViolatedLicense; } if (resets >= 2) { TraceLog((CdromSecError, "DvdDecode: Reset count was invalid\n")); goto ViolatedLicense; } TraceLog((CdromSecInfo, "DvdDecode: Successfully validated stored data\n")); *RegionMask = region; *ResetCount = resets; return STATUS_SUCCESS; ViolatedLicense: *RegionMask = 0x00; *ResetCount = 0x00; return STATUS_LICENSE_VIOLATION; } STATIC NTSTATUS SecureDvdGetSettingsCallBack( IN PWSTR ValueName, IN ULONG ValueType, IN PVOID ValueData, IN ULONG ValueLength, IN PVOID UnusedContext, IN PDVD_REGISTRY_CONTEXT Context ) { ULONGLONG hash = 0; NTSTATUS status; if (ValueType != REG_QWORD) { TraceLog((CdromSecError, "DvdGetSettingsCallback: Not REG_BINARY\n")); goto ViolatedLicense; } if (ValueLength != sizeof(ULONGLONG)) { TraceLog((CdromSecError, "DvdGetSettingsCallback: DVD Settings data too " "small (%x bytes)\n", ValueLength)); goto ViolatedLicense; } hash = *((PULONGLONG)ValueData); if (hash == INVALID_HASH) { TraceLog((CdromSecError, "DvdGetSettingsCallback: Invalid hash stored?\n")); goto ViolatedLicense; } // // validate the data // this also sets the values in the context upon success. // status = SecureDvdDecodeSettings(Context->DpidHash, Context->DriveHash, hash, &Context->RegionMask, &Context->ResetCount); if (status == STATUS_LICENSE_VIOLATION) { TraceLog((CdromSecError, "DvdGetSettingsCallback: data was violated!\n")); goto ViolatedLicense; } // // the above call to SecureDvdDecodeSettings can only return // success or a license violation // ASSERT(NT_SUCCESS(status)); return STATUS_SUCCESS; ViolatedLicense: Context->DriveHash = INVALID_HASH; Context->DpidHash = INVALID_HASH; Context->RegionMask = 0; Context->ResetCount = 0; return STATUS_LICENSE_VIOLATION; } STATIC NTSTATUS SecureDvdGetDigitalProductIdCallBack( IN PWSTR ValueName, IN ULONG ValueType, IN PDIGITALPID DigitalPid, // ValueData IN ULONG ValueLength, IN PVOID UnusedVariable, IN PULONGLONG DpidHash ) // validated for non-REG_BINARY // validated for good data // validated for short data { NTSTATUS status = STATUS_LICENSE_VIOLATION; ULONGLONG hash = 0; if (ValueType != REG_BINARY) { TraceLog((CdromSecError, "DvdDPIDCallback: Not REG_BINARY\n")); *DpidHash = INVALID_HASH; return STATUS_LICENSE_VIOLATION; } if (ValueLength < 4*sizeof(ULONGLONG)) { TraceLog((CdromSecError, "DvdDPIDCallback: DPID data too small (%x bytes)\n", ValueLength)); *DpidHash = INVALID_HASH; return STATUS_LICENSE_VIOLATION; } // // apparently, only 13 bytes of the DigitalPID are // going to stay static across upgrades. even these // will change if the boot hard drive, video card, or // bios signature changes. nonetheless, this is only // supposed to keep the honest people honest. :) // // // 8 bytes to fill == 64 bytes (need to rotate at least 48 bits) // TraceLog((CdromSecInfo, "Bios %08x Video %08x VolSer %08x\n", DigitalPid->dwBiosChecksumStatic, DigitalPid->dwVideoBiosChecksumStatic, DigitalPid->dwVolSerStatic)); hash ^= DigitalPid->dwBiosChecksumStatic; // 4 bytes // bios signature hash = RotateULongLong(hash, 13); // prime number hash ^= DigitalPid->dwVideoBiosChecksumStatic; // 4 bytes // video card hash = RotateULongLong(hash, 13); // prime number hash ^= DigitalPid->dwVolSerStatic; // 4 bytes // hard drive hash = RotateULongLong(hash, 13); // prime number *DpidHash = hash; return STATUS_SUCCESS; } STATIC NTSTATUS SecureDvdReturnDPIDHash( PULONGLONG DpidHash ) { RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0}; NTSTATUS status; // cannot be PAGED_CODE() because queryTable cannot be swapped out! // // query the value // queryTable[0].Name = L"DigitalProductId"; queryTable[0].EntryContext = DpidHash; queryTable[0].DefaultType = 0; queryTable[0].DefaultData = NULL; queryTable[0].DefaultLength = 0; queryTable[0].Flags = RTL_QUERY_REGISTRY_REQUIRED; queryTable[0].QueryRoutine = SecureDvdGetDigitalProductIdCallBack; *DpidHash = INVALID_HASH; status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE, L"\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion", &(queryTable[0]), NULL, NULL); if (status == STATUS_LICENSE_VIOLATION) { TraceLog((CdromSecError, "DvdReturnDPIDHash: Invalid DPID!\n")); } else if (!NT_SUCCESS(status)) { TraceLog((CdromSecError, "DvdReturnDPIDHash: Cannot get DPID (%x)\n", status)); } else { TraceLog((CdromSecInfo, "DvdReturnDPIDHash: Hash is now %I64x\n", *DpidHash)); } return status; } //////////////////////////////////////////////////////////////////////////////// //// Everything past here has not been component tested //////////////////////////////////////////////////////////////////////////////// #define SECURE_DVD_SET_SECURITY_ON_HANDLE 0 STATIC NTSTATUS SecureDvdSetHandleSecurity( IN HANDLE Handle ) { #if SECURE_DVD_SET_SECURITY_ON_HANDLE PACL newAcl = NULL; ULONG newAclSize; SECURITY_DESCRIPTOR securityDescriptor = { 0 }; NTSTATUS status; // // from \nt\private\ntos\io\pnpinit.c // //SeEnableAccessToExports(); TRY { newAclSize = sizeof(ACL); newAclSize += sizeof(ACCESS_ALLOWED_ACE); newAclSize -= sizeof(ULONG); newAclSize += RtlLengthSid(SeExports->SeLocalSystemSid); newAcl = ExAllocatePoolWithTag(PagedPool, newAclSize, DVD_TAG_SECURITY); if (newAcl == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; LEAVE; } status = RtlCreateSecurityDescriptor(&securityDescriptor, SECURITY_DESCRIPTOR_REVISION); if (!NT_SUCCESS(status)) { ASSERT(!"failed to create a security descriptor?"); LEAVE; } status = RtlCreateAcl(newAcl, newAclSize, ACL_REVISION); if (!NT_SUCCESS(status)) { ASSERT(!"failed to create a new ACL?"); LEAVE; } status = RtlAddAccessAllowedAce(newAcl, ACL_REVISION, KEY_ALL_ACCESS, SeExports->SeLocalSystemSid); if (!NT_SUCCESS(status)) { ASSERT(!"failed to add LocalSystem to ACL"); LEAVE; } status = RtlSetDaclSecurityDescriptor(&securityDescriptor, TRUE, newAcl, FALSE); if (!NT_SUCCESS(status)) { ASSERT(!"failed to set acl in security descriptor?"); LEAVE; } status = RtlValidSecurityDescriptor(&securityDescriptor); if (!NT_SUCCESS(status)) { ASSERT(!"failed to validate security descriptor?"); LEAVE; } status = ZwSetSecurityObject(Handle, // PROTECTED_DACL_SECURITY_INFORMATION, DACL_SECURITY_INFORMATION, &securityDescriptor); if (!NT_SUCCESS(status)) { ASSERT(!"Failed to set security on handle\n"); LEAVE; } status = STATUS_SUCCESS; } FINALLY { if (newAcl != NULL) { ExFreePool(newAcl); newAcl = NULL; } } #endif return STATUS_SUCCESS; } STATIC NTSTATUS SecureDvdGetRegistryHandle( IN ULONGLONG DpidHash, OUT PHANDLE Handle ) { OBJECT_ATTRIBUTES objectAttributes = {0}; UNICODE_STRING hashString = {0}; NTSTATUS status; LONG i; // // using char[] instead of char* allows modification of the // string in this routine (a way of obfuscating the string) // 0 ....+.... 1....+.. ..2....+. ...3....+. ...4 WCHAR string[] = L"\\Registry\\Machine\\Software\\Microsoft\\Windows NT"; WCHAR *hash = &(string[37]); PULONGLONG hashAsUlonglong = (PULONGLONG)hash; for (i = 0; i < sizeof(ULONGLONG); i++) { UCHAR temp; temp = (UCHAR)(DpidHash >> (8*i)); SET_FLAG(temp, 0x20); // more than 32 CLEAR_FLAG(temp, 0x80); // less than 128 hash[i] = (WCHAR)temp; // make it a wide char } hash[i] = UNICODE_NULL; RtlInitUnicodeString(&hashString, string); RtlZeroMemory(&objectAttributes, sizeof(OBJECT_ATTRIBUTES)); InitializeObjectAttributes(&objectAttributes, &hashString, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, RTL_REGISTRY_ABSOLUTE, // NULL? NULL // no security descriptor ); status = ZwCreateKey(Handle, KEY_ALL_ACCESS, &objectAttributes, 0, NULL, // can be a unicode string.... REG_OPTION_NON_VOLATILE, NULL); if (!NT_SUCCESS(status)) { TraceLog((CdromSecError, "DvdGetRegistryHandle: Failed to create key (%x)\n", status)); return status; } status = SecureDvdSetHandleSecurity(*Handle); if (!NT_SUCCESS(status)) { TraceLog((CdromSecError, "DvdGetRegistryHandle: Failed to set key security (%x)\n", status)); ZwClose(*Handle); *Handle = INVALID_HANDLE_VALUE; } return status; } STATIC VOID SecureDvdCreateValueNameFromHash( IN ULONGLONG DriveHash, OUT PWCHAR HashString ) { PUCHAR buffer = (PUCHAR)HashString; LONG i; RtlZeroMemory(HashString, 17*sizeof(WCHAR)); sprintf(buffer, "%016I64x", DriveHash); // now massage the data to be unicode for (i = 15; i >= 0; i--) { HashString[i] = buffer[i]; } } STATIC NTSTATUS SecureDvdReadOrWriteRegionAndResetCount( IN PDEVICE_OBJECT Fdo, IN UCHAR NewRegion, IN BOOLEAN ReadingTheValues ) // // NewRegion is ignored if ReadingTheValues is TRUE // { PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension; PCDROM_DATA cddata; NTSTATUS status; ULONG keyDisposition; DVD_REGISTRY_CONTEXT registryContext = {0}; HANDLE semiSecureHandle = INVALID_HANDLE_VALUE; PAGED_CODE(); ASSERT(commonExtension->IsFdo); cddata = (PCDROM_DATA)(commonExtension->DriverData); if (cddata->DvdRpc0LicenseFailure) { TraceLog((CdromSecError, "Dvd%sSettings: Already violated licensing\n", (ReadingTheValues ? "Read" : "Write") )); goto ViolatedLicense; } RtlZeroMemory(®istryContext, sizeof(DVD_REGISTRY_CONTEXT)); // // then, get the DigitalProductIdHash and this DriveHash // { status = SecureDvdReturnDPIDHash(®istryContext.DpidHash); // if this fails, we are in serious trouble! if (status == STATUS_LICENSE_VIOLATION) { TraceLog((CdromSecError, "Dvd%sSettings: License error getting DPIDHash?\n", (ReadingTheValues ? "Read" : "Write"))); goto ViolatedLicense; } else if (!NT_SUCCESS(status)) { TraceLog((CdromSecError, "Dvd%sSettings: Couldn't get DPID Hash! (%x)\n", (ReadingTheValues ? "Read" : "Write"), status)); goto RetryExit; } if (registryContext.DpidHash == INVALID_HASH) { goto ErrorExit; } registryContext.DriveHash = SecureDvdGetDriveHash(fdoExtension->DeviceDescriptor); if (registryContext.DriveHash == INVALID_HASH) { TraceLog((CdromSecError, "Dvd%sSettings: Couldn't create drive hash(!)\n", (ReadingTheValues ? "Read" : "Write"))); goto ErrorExit; } } // // finally get a handle based upon the DigitalProductIdHash // to our "semi-secure" registry key, creating it if neccessary. // status= SecureDvdGetRegistryHandle(registryContext.DpidHash, &semiSecureHandle); if (!NT_SUCCESS(status)) { TraceLog((CdromSecError, "Dvd%sSettings: Could not get semi-secure handle %x\n", (ReadingTheValues ? "Read" : "Write"), status)); goto ErrorExit; } // // if reading the values, use the semi-secure handle to open a subkey, // read its data, close the handle, it. // // if (ReadingTheValues) { WCHAR hashString[17] = {0}; // 16 + NULL RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0}; SecureDvdCreateValueNameFromHash(registryContext.DriveHash, hashString); RtlZeroMemory(&queryTable[0], 2*sizeof(RTL_QUERY_REGISTRY_TABLE)); queryTable[0].DefaultData = NULL; queryTable[0].DefaultLength = 0; queryTable[0].DefaultType = 0; queryTable[0].EntryContext = ®istryContext; queryTable[0].Flags = RTL_QUERY_REGISTRY_REQUIRED; queryTable[0].Name = hashString; queryTable[0].QueryRoutine = SecureDvdGetSettingsCallBack; status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE, semiSecureHandle, &queryTable[0], ®istryContext, NULL); if (status == STATUS_LICENSE_VIOLATION) { TraceLog((CdromSecError, "Dvd%sSettings: Invalid value in registry!\n", (ReadingTheValues ? "Read" : "Write"))); goto ViolatedLicense; } else if (!NT_SUCCESS(status)) { TraceLog((CdromSecError, "Dvd%sSettings: Other non-license error (%x)\n", (ReadingTheValues ? "Read" : "Write"), status)); goto ErrorExit; } // // set the real values.... // cddata->Rpc0SystemRegion = registryContext.RegionMask; cddata->Rpc0SystemRegionResetCount = registryContext.ResetCount; // // everything is kosher! // TraceLog((CdromSecInfo, "Dvd%sSettings: Region %x Reset %x\n", (ReadingTheValues ? "Read" : "Write"), cddata->Rpc0SystemRegion, cddata->Rpc0SystemRegionResetCount)); } else { // !ReadingTheValues, iow, writing them.... // // if writing the values, obfuscate them first (which also validates), // then use the semi-secure handle to write the subkey // WCHAR hashString[17] = {0}; // 16 + NULL ULONGLONG obfuscated; // // don't munge the device extension until we modify the registry // (see below for modification of device extension data) // registryContext.RegionMask = NewRegion; registryContext.ResetCount = cddata->Rpc0SystemRegionResetCount-1; // // this also validates the settings // SecureDvdCreateValueNameFromHash(registryContext.DriveHash, hashString); status = SecureDvdEncodeSettings(registryContext.DpidHash, registryContext.DriveHash, &obfuscated, registryContext.RegionMask, registryContext.ResetCount); if (status == STATUS_LICENSE_VIOLATION) { TraceLog((CdromSecError, "Dvd%sSettings: User may have modified memory! " "%x %x\n", (ReadingTheValues ? "Read" : "Write"), registryContext.RegionMask, registryContext.ResetCount)); goto ViolatedLicense; } else if (!NT_SUCCESS(status)) { TraceLog((CdromSecError, "Dvd%sSettings: Couldn't obfuscate data %x %x\n", (ReadingTheValues ? "Read" : "Write"), registryContext.RegionMask, registryContext.ResetCount)); goto ErrorExit; } // // save them for posterity // TraceLog((CdromSecInfo, "Dvd%sSettings: Data is %016I64x\n", (ReadingTheValues ? "Read" : "Write"), obfuscated)); status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE, semiSecureHandle, hashString, REG_QWORD, &obfuscated, (ULONG)(sizeof(ULONGLONG)) ); if (!NT_SUCCESS(status)) { TraceLog((CdromSecError, "Dvd%sSettings: Couldn't save %x\n", (ReadingTheValues ? "Read" : "Write"), status)); goto ErrorExit; } // // make the change in the device extension data also // cddata->Rpc0SystemRegion = NewRegion; cddata->Rpc0SystemRegionResetCount--; TraceLog((CdromSecInfo, "Dvd%sSettings: Region %x Reset %x\n", (ReadingTheValues ? "Read" : "Write"), cddata->Rpc0SystemRegion, cddata->Rpc0SystemRegionResetCount)); } if (semiSecureHandle != INVALID_HANDLE_VALUE) { ZwClose(semiSecureHandle); } return STATUS_SUCCESS; ViolatedLicense: { PIO_ERROR_LOG_PACKET errorLogEntry; if (semiSecureHandle != INVALID_HANDLE_VALUE) { ZwClose(semiSecureHandle); } /* errorLogEntry = (PIO_ERROR_LOG_ENTRY) IoAllocateErrorLogEntry(Fdo, (UCHAR)(sizeof(IO_ERROR_LOG_PACKET))); if (errorLogEntry != NULL) { errorLogEntry->FinalStatus = STATUS_LICENSE_VIOLATION; errorLogEntry->ErrorCode = STATUS_LICENSE_VIOLATION; errorLogEntry->MajorFunctionCode = IRP_MJ_START_DEVICE; IoWriteErrorLogEntry(errorLogEntry); } */ TraceLog((CdromSecError, "Dvd%sSettings: License Violation Detected\n", (ReadingTheValues ? "Read" : "Write"))); cddata->DvdRpc0LicenseFailure = TRUE; // no playback cddata->Rpc0SystemRegion = 0xff; // no regions cddata->Rpc0SystemRegionResetCount = 0; // no resets return STATUS_LICENSE_VIOLATION; } RetryExit: if (ReadingTheValues) { cddata->Rpc0RetryRegistryCallback = 1; } // // fall-through to Error Exit... // ErrorExit: TraceLog((CdromSecError, "Dvd%sSettings: Non-License Error Detected\n", (ReadingTheValues ? "Read" : "Write"))); // // don't modify the device extension on non-license-violation errors // if (semiSecureHandle != INVALID_HANDLE_VALUE) { ZwClose(semiSecureHandle); } return STATUS_UNSUCCESSFUL; } //////////////////////////////////////////////////////////////////////////////// // // The following functions are externally accessible. They therefore cannot // be either STATIC nor INLINE // static to make debugging more difficult in the shipping versions. // // These exports return one of only three NTSTATUS values: // STATUS_SUCCESS // STATUS_UNSUCCESSFUL // STATUS_LICENSE_VIOLATION // //////////////////////////////////////////////////////////////////////////////// NTSTATUS CdRomGetRpc0Settings( IN PDEVICE_OBJECT Fdo ) { PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension; PCDROM_DATA cddata = (PCDROM_DATA)(commonExtension->DriverData); NTSTATUS status; KeWaitForMutexObject(&cddata->Rpc0RegionMutex, UserRequest, KernelMode, FALSE, NULL); status = SecureDvdReadOrWriteRegionAndResetCount(Fdo, 0, TRUE); KeReleaseMutex(&cddata->Rpc0RegionMutex, FALSE); return status; } NTSTATUS CdRomSetRpc0Settings( IN PDEVICE_OBJECT Fdo, IN UCHAR NewRegion ) { PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension; PCDROM_DATA cddata = (PCDROM_DATA)(commonExtension->DriverData); NTSTATUS status; KeWaitForMutexObject(&cddata->Rpc0RegionMutex, UserRequest, KernelMode, FALSE, NULL); status = SecureDvdReadOrWriteRegionAndResetCount(Fdo, NewRegion, FALSE); KeReleaseMutex(&cddata->Rpc0RegionMutex, FALSE); return status; } #if 0 // @@END_DDKSPLIT NTSTATUS CdRomGetRpc0Settings( IN PDEVICE_OBJECT Fdo ) { PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension; PCDROM_DATA cddata = (PCDROM_DATA)(commonExtension->DriverData); cddata->Rpc0SystemRegion = (UCHAR)(~1); // region one cddata->Rpc0SystemRegionResetCount = 0; // no resets return STATUS_SUCCESS; } NTSTATUS CdRomSetRpc0Settings( IN PDEVICE_OBJECT Fdo, IN UCHAR NewRegion ) { return STATUS_SUCCESS; } // @@BEGIN_DDKSPLIT #endif // 0 -- DDK stub for all the stuff we do... // @@END_DDKSPLIT