mirror of
https://github.com/aaru-dps/Aaru.Decryption.git
synced 2025-12-16 19:24:33 +00:00
Find CSS keys from pattern attack
This commit is contained in:
@@ -33,8 +33,8 @@
|
|||||||
<NoWarn>CS1591;CS1574</NoWarn>
|
<NoWarn>CS1591;CS1574</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<InternalsVisibleTo Include="Aaru.Tests"/>
|
<InternalsVisibleTo Include="Aaru.Tests" />
|
||||||
<InternalsVisibleTo Include="Aaru.Tests.Devices"/>
|
<InternalsVisibleTo Include="Aaru.Tests.Devices" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<NrtRevisionFormat>$(Version)+{chash:8}</NrtRevisionFormat>
|
<NrtRevisionFormat>$(Version)+{chash:8}</NrtRevisionFormat>
|
||||||
@@ -43,9 +43,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Aaru.Decoders\Aaru.Decoders.csproj"/>
|
<ProjectReference Include="..\Aaru.Decoders\Aaru.Decoders.csproj" />
|
||||||
<ProjectReference Include="..\Aaru.Devices\Aaru.Devices.csproj"/>
|
<ProjectReference Include="..\Aaru.Devices\Aaru.Devices.csproj" />
|
||||||
<ProjectReference Include="..\Aaru.Images\Aaru.Images.csproj"/>
|
<ProjectReference Include="..\Aaru.Images\Aaru.Images.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
404
DVD/CSS.cs
404
DVD/CSS.cs
@@ -44,7 +44,12 @@
|
|||||||
// libdvdcss (https://www.videolan.org/developers/libdvdcss.html)
|
// libdvdcss (https://www.videolan.org/developers/libdvdcss.html)
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Aaru.CommonTypes;
|
||||||
|
using Aaru.CommonTypes.Enums;
|
||||||
|
using Aaru.CommonTypes.Interfaces;
|
||||||
|
using Aaru.CommonTypes.Structs;
|
||||||
using Aaru.Decoders.DVD;
|
using Aaru.Decoders.DVD;
|
||||||
|
|
||||||
namespace Aaru.Decryption.DVD;
|
namespace Aaru.Decryption.DVD;
|
||||||
@@ -544,11 +549,11 @@ public class CSS
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Takes an encrypted key and its crypto and returns the key decrypted.</summary>
|
/// <summary>Takes an encrypted key and its crypto and returns the key decrypted.</summary>
|
||||||
/// <param name="invert"></param>
|
/// <param name="invert">For disc keys, invert is <c>0x00</c>. For title keys, invert if <c>0xff</c>.</param>
|
||||||
/// <param name="cryptoKey">The key used to encrypt the data.</param>
|
/// <param name="cryptoKey">The key used to encrypt the data.</param>
|
||||||
/// <param name="encryptedKey">The encrypted data.</param>
|
/// <param name="encryptedKey">The encrypted data.</param>
|
||||||
/// <param name="decryptedKey">The decrypted data.</param>
|
/// <param name="decryptedKey">The decrypted data.</param>
|
||||||
public static void DecryptKey(byte invert, byte[] cryptoKey, byte[] encryptedKey, out byte[] decryptedKey)
|
static void DecryptKey(byte invert, byte[] cryptoKey, byte[] encryptedKey, out byte[] decryptedKey)
|
||||||
{
|
{
|
||||||
decryptedKey = new byte[5];
|
decryptedKey = new byte[5];
|
||||||
byte[] k = new byte[5];
|
byte[] k = new byte[5];
|
||||||
@@ -590,8 +595,8 @@ public class CSS
|
|||||||
decryptedKey[0] = (byte)(k[0] ^ _cssTable1[decryptedKey[0]]);
|
decryptedKey[0] = (byte)(k[0] ^ _cssTable1[decryptedKey[0]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DecryptTitleKey(byte invert, byte[] cryptoKey, byte[] encryptedKey, out byte[] decryptedKey) =>
|
public static void DecryptTitleKey(byte[] cryptoKey, byte[] encryptedKey, out byte[] decryptedKey) =>
|
||||||
DecryptKey(invert, cryptoKey, encryptedKey, out decryptedKey);
|
DecryptKey(0xff, cryptoKey, encryptedKey, out decryptedKey);
|
||||||
|
|
||||||
/// <summary>Takes an bytearray of encrypted keys, decrypts them and returns the correctly decrypted key.</summary>
|
/// <summary>Takes an bytearray of encrypted keys, decrypts them and returns the correctly decrypted key.</summary>
|
||||||
/// <param name="encryptedKeys">Encrypted keys to try to decrypt.</param>
|
/// <param name="encryptedKeys">Encrypted keys to try to decrypt.</param>
|
||||||
@@ -630,65 +635,115 @@ public class CSS
|
|||||||
/// <param name="blocks">Number of sectors in <c>sectorData</c>.</param>
|
/// <param name="blocks">Number of sectors in <c>sectorData</c>.</param>
|
||||||
/// <param name="blockSize">Size of one sector.</param>
|
/// <param name="blockSize">Size of one sector.</param>
|
||||||
/// <returns>The decrypted sector.</returns>
|
/// <returns>The decrypted sector.</returns>
|
||||||
public static byte[] DecryptSector(byte[] sectorData, byte[] cmiData, byte[] keyData, uint blocks = 1,
|
public static byte[] DecryptSector(byte[] sectorData, byte[] keyData, byte[]? cmiData, uint blocks = 1,
|
||||||
uint blockSize = 2048)
|
uint blockSize = 2048)
|
||||||
{
|
{
|
||||||
if(cmiData.All(cmi => (cmi & 0x80) >> 7 == 0) ||
|
// None of the sectors are encrypted
|
||||||
keyData.All(k => k == 0))
|
if((cmiData != null && cmiData.All(static cmi => (cmi & 0x80) >> 7 == 0)) ||
|
||||||
|
keyData.All(static k => k == 0))
|
||||||
return sectorData;
|
return sectorData;
|
||||||
|
|
||||||
byte[] decryptedBuffer = new byte[sectorData.Length];
|
byte[] decryptedBuffer = new byte[sectorData.Length];
|
||||||
|
|
||||||
for(uint j = 0; j < blocks; j++)
|
for(uint i = 0; i < blocks; i++)
|
||||||
{
|
{
|
||||||
byte[] currentKey = keyData.Skip((int)(j * 5)).Take(5).ToArray();
|
byte[] currentKey = keyData.Skip((int)(i * 5)).Take(5).ToArray();
|
||||||
byte[] currentSector = sectorData.Skip((int)(j * blockSize)).Take((int)blockSize).ToArray();
|
byte[] currentSector = sectorData.Skip((int)(i * blockSize)).Take((int)blockSize).ToArray();
|
||||||
|
|
||||||
// If the CMI tells use the sector isn't encrypted or
|
if(!IsEncrypted(cmiData?[i], currentKey, currentSector))
|
||||||
// if the key is all zeroes or
|
|
||||||
// if the MPEG Packetized Elementary Stream scrambling control value tells us the packet is not scrambled
|
|
||||||
if((cmiData[j] & 0x80) >> 7 == 0 ||
|
|
||||||
currentKey.All(k => k == 0) ||
|
|
||||||
(currentSector[20] & 0x30) >> 4 == 0)
|
|
||||||
{
|
{
|
||||||
// Sector is not encrypted
|
Array.Copy(currentSector, 0, decryptedBuffer, (int)(i * blockSize), blockSize);
|
||||||
Array.Copy(currentSector, 0, decryptedBuffer, (int)(j * blockSize), blockSize);
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint lfsr1Lo = (uint)(currentKey[0] ^ currentSector[0x54]) | 0x100;
|
Array.Copy(UnscrambleSector(currentKey, currentSector), 0, decryptedBuffer, (int)(i * blockSize),
|
||||||
uint lfsr1Hi = (uint)currentKey[1] ^ currentSector[0x55];
|
blockSize);
|
||||||
|
|
||||||
uint lfsr0 = (uint)((currentKey[2] | (currentKey[3] << 8) | (currentKey[4] << 16)) ^
|
|
||||||
(sectorData[0x56] | (sectorData[0x57] << 8) | (sectorData[0x58] << 16)));
|
|
||||||
|
|
||||||
uint oLfsr1 = lfsr0 & 7;
|
|
||||||
lfsr0 = (lfsr0 * 2) + 8 - oLfsr1;
|
|
||||||
|
|
||||||
uint combined = 0;
|
|
||||||
|
|
||||||
for(uint i = 128; i < blockSize; i++)
|
|
||||||
{
|
|
||||||
oLfsr1 = (uint)(_cssTable2[lfsr1Hi] ^ _cssTable3[lfsr1Lo]);
|
|
||||||
lfsr1Hi = lfsr1Lo >> 1;
|
|
||||||
lfsr1Lo = ((lfsr1Lo & 1) << 8) ^ oLfsr1;
|
|
||||||
oLfsr1 = _cssTable5[oLfsr1];
|
|
||||||
uint oLfsr0 = (((((((lfsr0 >> 3) ^ lfsr0) >> 1) ^ lfsr0) >> 8) ^ lfsr0) >> 5) & 0xff;
|
|
||||||
lfsr0 = (lfsr0 >> 8) | (oLfsr0 << 24);
|
|
||||||
lfsr0 = (lfsr0 << 8) | oLfsr0;
|
|
||||||
oLfsr0 = _cssTable4[oLfsr0];
|
|
||||||
combined += oLfsr0 + oLfsr1;
|
|
||||||
currentSector[i] = (byte)(_cssTable1[currentSector[i]] ^ (combined & 0xff));
|
|
||||||
combined >>= 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
Array.Copy(currentSector, 0, decryptedBuffer, (int)(j * blockSize), blockSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return decryptedBuffer;
|
return decryptedBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unscrambles a DVD sector with a title key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The title key.</param>
|
||||||
|
/// <param name="sector">The scrambled sector.</param>
|
||||||
|
/// <returns>The unscrambled sector.</returns>
|
||||||
|
static byte[] UnscrambleSector(IReadOnlyList<byte> key, byte[] sector)
|
||||||
|
{
|
||||||
|
long lfsr1Lo = (key[0] ^ sector[0x54]) | 0x100;
|
||||||
|
long lfsr1Hi = key[1] ^ sector[0x55];
|
||||||
|
|
||||||
|
long lfsr0 = (key[2] | (key[3] << 8) | (key[4] << 16)) ^
|
||||||
|
(sector[0x56] | (sector[0x57] << 8) | (sector[0x58] << 16));
|
||||||
|
|
||||||
|
long oLfsr1 = lfsr0 & 7;
|
||||||
|
lfsr0 = (lfsr0 * 2) + 8 - oLfsr1;
|
||||||
|
|
||||||
|
long combined = 0;
|
||||||
|
|
||||||
|
for(uint i = 0x80; i < 2048; i++)
|
||||||
|
{
|
||||||
|
oLfsr1 = _cssTable2[lfsr1Hi] ^ _cssTable3[lfsr1Lo];
|
||||||
|
lfsr1Hi = lfsr1Lo >> 1;
|
||||||
|
lfsr1Lo = ((lfsr1Lo & 1) << 8) ^ oLfsr1;
|
||||||
|
oLfsr1 = _cssTable5[oLfsr1];
|
||||||
|
long oLfsr0 = (((((((lfsr0 >> 3) ^ lfsr0) >> 1) ^ lfsr0) >> 8) ^ lfsr0) >> 5) & 0xff;
|
||||||
|
lfsr0 = (lfsr0 << 8) | oLfsr0;
|
||||||
|
oLfsr0 = _cssTable4[oLfsr0];
|
||||||
|
combined += oLfsr0 + oLfsr1;
|
||||||
|
sector[i] = (byte)(_cssTable1[sector[i]] ^ (combined & 0xff));
|
||||||
|
combined >>= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we unscrambled the sector, we need to set the MPEG Packetized Elementary Stream
|
||||||
|
// scrambling control value to "not scrambled".
|
||||||
|
sector[20] ^= 1 << 4;
|
||||||
|
|
||||||
|
return sector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Analyzes data to try to figure out if the sector is encrypted, including
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>If the packet is not an MPEG packet</item>
|
||||||
|
/// <item>If the CMI tells us the sector isn't encrypted</item>
|
||||||
|
/// <item>If the key is all zeroes</item>
|
||||||
|
/// <item>If the MPEG Packetized Elementary Stream scrambling control value tells us the packet is not scrambled</item>
|
||||||
|
/// <item>If if the packet is system_header, padding_stream or private_stream2 (cannot be encrypted according to libdvdcss)</item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cmi">The Copyright Management Information.</param>
|
||||||
|
/// <param name="key">The title key.</param>
|
||||||
|
/// <param name="sector">The sector data.</param>
|
||||||
|
/// <returns><c>True</c> if encrypted</returns>
|
||||||
|
static bool IsEncrypted(byte? cmi, byte[]? key, IReadOnlyList<byte> sector)
|
||||||
|
{
|
||||||
|
// Only MPEG packets can be encrypted.
|
||||||
|
if(!MPEG.IsMpegPacket(sector))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// The CMI tells us the sector is not encrypted.
|
||||||
|
if(cmi != null &&
|
||||||
|
(cmi & 0x80) >> 7 == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// We have the key but it's all zeroes, so sector is unencrypted.
|
||||||
|
if(key != null &&
|
||||||
|
key.All(static k => k == 0))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// These packet types cannot be encrypted
|
||||||
|
if(sector[17] == (byte)MPEG.Mpeg2StreamId.SystemHeader ||
|
||||||
|
sector[17] == (byte)MPEG.Mpeg2StreamId.PaddingStream ||
|
||||||
|
sector[17] == (byte)MPEG.Mpeg2StreamId.PrivateStream2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// MPEG Packetized Elementary Stream scrambling control value
|
||||||
|
return (sector[20] & 0x30) >> 4 == 1;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Takes an RPC state from the drive and a CMI from a disc and checks if the regions are compatible.</summary>
|
/// <summary>Takes an RPC state from the drive and a CMI from a disc and checks if the regions are compatible.</summary>
|
||||||
/// <param name="rpc">The <c>RegionalPlaybackControlState</c> from drive.</param>
|
/// <param name="rpc">The <c>RegionalPlaybackControlState</c> from drive.</param>
|
||||||
/// <param name="cmi">The <c>LeadInCopyright</c> from disc.</param>
|
/// <param name="cmi">The <c>LeadInCopyright</c> from disc.</param>
|
||||||
@@ -708,4 +763,263 @@ public class CSS
|
|||||||
((rpc.RegionMask & 0x40) == (cmi.RegionInformation & 0x40) && (rpc.RegionMask & 0x40) != 0x40) ||
|
((rpc.RegionMask & 0x40) == (cmi.RegionInformation & 0x40) && (rpc.RegionMask & 0x40) != 0x40) ||
|
||||||
((rpc.RegionMask & 0x80) == (cmi.RegionInformation & 0x80) && (rpc.RegionMask & 0x80) != 0x80);
|
((rpc.RegionMask & 0x80) == (cmi.RegionInformation & 0x80) && (rpc.RegionMask & 0x80) != 0x80);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This tries to find a title key for a range of sectors by doing a brute force pattern search developed by
|
||||||
|
/// Ethan Hawke of DeCSSPlus. CSS encrypted sectors have parts of them that are unencrypted (byte 0x0 - 0x80).
|
||||||
|
/// We try to find a long pattern of repeated bytes just before the encryption starts. If we assume this
|
||||||
|
/// pattern continues into the encrypted part, we can force keys until one of them satisfies this condition.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sector">The sector to analyze.</param>
|
||||||
|
/// <param name="key">The key found.</param>
|
||||||
|
/// <returns><c>true</c> if a key was found.</returns>
|
||||||
|
static bool AttackPattern(byte[] sector, out byte[] key)
|
||||||
|
{
|
||||||
|
uint bestPatternLength = 0;
|
||||||
|
uint bestPattern = 0;
|
||||||
|
key = new byte[5];
|
||||||
|
|
||||||
|
for(uint i = 2; i < 0x30; i++)
|
||||||
|
{
|
||||||
|
// Find the number of bytes that repeats in cycles.
|
||||||
|
for(uint j = i + 1; j < 0x80 && sector[0x7F - (j % i)] == sector[0x7F - j]; j++)
|
||||||
|
{
|
||||||
|
if(j <= bestPatternLength)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bestPatternLength = j;
|
||||||
|
bestPattern = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found an adequate pattern.
|
||||||
|
if(bestPattern <= 0 ||
|
||||||
|
bestPatternLength <= 3 ||
|
||||||
|
bestPatternLength / bestPattern < 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int offset = (int)(0x80 - ((bestPatternLength / bestPattern) * bestPattern));
|
||||||
|
|
||||||
|
int result = RecoverTitleKey(0, sector.Skip(0x80).Take(sector.Length - 0x80).ToArray(),
|
||||||
|
sector.Skip(offset).Take((int)(sector.Length - offset)).ToArray(),
|
||||||
|
sector.Skip(0x54).Take(5).ToArray(), out key);
|
||||||
|
|
||||||
|
return result >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Takes a guessed plain text and a encrypted bytes and tries to recover the title key
|
||||||
|
/// from those. Attack developed by Frank Stevenson.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">Start position.</param>
|
||||||
|
/// <param name="encryptedBytes">Buffer with encrypted bytes.</param>
|
||||||
|
/// <param name="decryptedBytes">Buffer with decrypted bytes.</param>
|
||||||
|
/// <param name="sectorSeed">This sector's seed values.</param>
|
||||||
|
/// <param name="key">The title key.</param>
|
||||||
|
/// <returns>Positive values on success.</returns>
|
||||||
|
static int RecoverTitleKey(uint start, byte[] encryptedBytes, byte[] decryptedBytes, byte[] sectorSeed,
|
||||||
|
out byte[] key)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[10];
|
||||||
|
long oLfsr1;
|
||||||
|
long oLfsr0;
|
||||||
|
long iTry;
|
||||||
|
uint i;
|
||||||
|
int exit = -1;
|
||||||
|
key = new byte[5];
|
||||||
|
|
||||||
|
for(i = 0; i < 10; i++)
|
||||||
|
buffer[i] = (byte)(_cssTable1[encryptedBytes[i]] ^ decryptedBytes[i]);
|
||||||
|
|
||||||
|
for(iTry = start; iTry < 0x10000; iTry++)
|
||||||
|
{
|
||||||
|
long lfsr1Lo = (iTry >> 8) | 0x100;
|
||||||
|
long lfsr1Hi = iTry & 0xff;
|
||||||
|
long lfsr0 = 0;
|
||||||
|
long combined = 0;
|
||||||
|
|
||||||
|
// Iterate cipher 4 times to reconstruct LFSR2
|
||||||
|
for(i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
// Advance LFSR1 normally
|
||||||
|
oLfsr1 = _cssTable2[lfsr1Hi] ^ _cssTable3[lfsr1Lo];
|
||||||
|
lfsr1Hi = lfsr1Lo >> 1;
|
||||||
|
lfsr1Lo = ((lfsr1Lo & 1) << 8) ^ oLfsr1;
|
||||||
|
oLfsr1 = _cssTable5[oLfsr1];
|
||||||
|
oLfsr0 = buffer[i];
|
||||||
|
|
||||||
|
if(combined > 0)
|
||||||
|
oLfsr0 = (oLfsr0 + 0xff) & 0x0ff;
|
||||||
|
|
||||||
|
if(oLfsr0 < oLfsr1)
|
||||||
|
oLfsr0 += 0x100;
|
||||||
|
|
||||||
|
oLfsr0 -= oLfsr1;
|
||||||
|
combined += oLfsr0 + oLfsr1;
|
||||||
|
oLfsr0 = _cssTable4[oLfsr0];
|
||||||
|
lfsr0 = (lfsr0 << 8) | oLfsr0;
|
||||||
|
combined >>= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
long candidate = lfsr0;
|
||||||
|
|
||||||
|
// Iterate 6 more times to validate candidate key
|
||||||
|
for(; i < 10; i++)
|
||||||
|
{
|
||||||
|
oLfsr1 = _cssTable2[lfsr1Hi] ^ _cssTable3[lfsr1Lo];
|
||||||
|
lfsr1Hi = lfsr1Lo >> 1;
|
||||||
|
lfsr1Lo = ((lfsr1Lo & 1) << 8) ^ oLfsr1;
|
||||||
|
oLfsr1 = _cssTable5[oLfsr1];
|
||||||
|
oLfsr0 = (((((((lfsr0 >> 3) ^ lfsr0) >> 1) ^ lfsr0) >> 8) ^ lfsr0) >> 5) & 0xff;
|
||||||
|
lfsr0 = (lfsr0 << 8) | oLfsr0;
|
||||||
|
oLfsr0 = _cssTable4[oLfsr0];
|
||||||
|
combined += oLfsr0 + oLfsr1;
|
||||||
|
|
||||||
|
if((combined & 0xff) != buffer[i])
|
||||||
|
break;
|
||||||
|
|
||||||
|
combined >>= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(i != 10)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
lfsr0 = candidate;
|
||||||
|
|
||||||
|
for(i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
lfsr1Lo = lfsr0 & 0xff;
|
||||||
|
lfsr0 = (lfsr0 >> 8);
|
||||||
|
|
||||||
|
for(uint j = 0; j < 256; j++)
|
||||||
|
{
|
||||||
|
lfsr0 = (lfsr0 & 0x1ffff) | (j << 17);
|
||||||
|
oLfsr0 = (((((((lfsr0 >> 3) ^ lfsr0) >> 1) ^ lfsr0) >> 8) ^ lfsr0) >> 5) & 0xff;
|
||||||
|
|
||||||
|
if(oLfsr0 == lfsr1Lo)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oLfsr1 = (lfsr0 >> 1) - 4;
|
||||||
|
|
||||||
|
for(combined = 0; combined < 8; combined++)
|
||||||
|
{
|
||||||
|
if(((oLfsr1 + combined) * 2) + 8 - ((oLfsr1 + combined) & 7) != lfsr0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
key[0] = (byte)(iTry >> 8);
|
||||||
|
key[1] = (byte)(iTry & 0xFF);
|
||||||
|
key[2] = (byte)(((oLfsr1 + combined) >> 0) & 0xFF);
|
||||||
|
key[3] = (byte)(((oLfsr1 + combined) >> 8) & 0xFF);
|
||||||
|
key[4] = (byte)(((oLfsr1 + combined) >> 16) & 0xFF);
|
||||||
|
exit = (int)(iTry + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(exit < 0)
|
||||||
|
return exit;
|
||||||
|
|
||||||
|
key[0] ^= sectorSeed[0];
|
||||||
|
key[1] ^= sectorSeed[1];
|
||||||
|
key[2] ^= sectorSeed[2];
|
||||||
|
key[3] ^= sectorSeed[3];
|
||||||
|
key[4] ^= sectorSeed[4];
|
||||||
|
|
||||||
|
return exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to find a title key by attacking CSS vulnerabilities.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input"><c>IOpticalMediaImage</c> to find the title key in.</param>
|
||||||
|
/// <param name="startSector">Sector index to begin search.</param>
|
||||||
|
/// <param name="sectorsToSearch">Amount of sectors to search before giving up.</param>
|
||||||
|
/// <returns>The title key.</returns>
|
||||||
|
static byte[] FindTitleKey(IOpticalMediaImage input, ulong startSector, ulong sectorsToSearch = 20000)
|
||||||
|
{
|
||||||
|
byte[] titleKey = new byte[5];
|
||||||
|
|
||||||
|
for(ulong i = 0; i < sectorsToSearch; i++)
|
||||||
|
{
|
||||||
|
input.ReadSector(startSector + i, out byte[] sector);
|
||||||
|
|
||||||
|
if(!IsEncrypted(null, null, sector))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(AttackPattern(sector, out byte[] key))
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return titleKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates title keys for all sectors in a track.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input"><c>IOpticalMediaImage</c> to generate keys for.</param>
|
||||||
|
/// <param name="partitions">List of <c>Partition</c> to analyze.</param>
|
||||||
|
/// <param name="trackSectors">Total number of sectors for track.</param>
|
||||||
|
/// <param name="pluginType"></param>
|
||||||
|
/// <returns>A byte array with keys for every sector in the track. One key is 5 bytes.</returns>
|
||||||
|
public static byte[] GenerateTitleKeys(IOpticalMediaImage input, List<Partition> partitions, ulong trackSectors,
|
||||||
|
Type pluginType)
|
||||||
|
{
|
||||||
|
byte[] keys = new byte[trackSectors * 5];
|
||||||
|
|
||||||
|
foreach(Partition partition in partitions)
|
||||||
|
{
|
||||||
|
if(Activator.CreateInstance(pluginType) is not IReadOnlyFilesystem fs)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(!HasVideoTsFolder(input, fs, partition))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(fs.Mount(input, partition, null, null, null) != ErrorNumber.NoError)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(fs.OpenDir("VIDEO_TS", out IDirNode node) == ErrorNumber.NoError)
|
||||||
|
{
|
||||||
|
while(fs.ReadDir(node, out string entry) == ErrorNumber.NoError &&
|
||||||
|
entry is not null)
|
||||||
|
{
|
||||||
|
if(!entry.ToLower().EndsWith(".vob"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fs.Stat("VIDEO_TS" + "/" + entry, out FileEntryInfo stat);
|
||||||
|
|
||||||
|
byte[] key = FindTitleKey(input, stat.Inode);
|
||||||
|
|
||||||
|
for(long i = 0; i < stat.Blocks; i++)
|
||||||
|
key.CopyTo(keys, (long)(5 * (stat.Inode + (ulong)i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.CloseDir(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.Unmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DVD video discs always have a <c>VIDEO_TS</c> folder. If it doesn't have one, it's not a DVD video.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input"><c>IOpticalMediaImage</c> to check for <c>VIDEO_TS</c> folder in.</param>
|
||||||
|
/// <param name="fs"><c>IReadOnlyFilesystem</c> to check in.</param>
|
||||||
|
/// <param name="partition"><c>Partition</c> to check in.</param>
|
||||||
|
/// <returns><c>true</c> if <c>VIDEO_TS</c> folder was found.</returns>
|
||||||
|
static bool HasVideoTsFolder(IOpticalMediaImage input, IReadOnlyFilesystem fs, Partition partition)
|
||||||
|
{
|
||||||
|
ErrorNumber error = fs.Mount(input, partition, null, null, null);
|
||||||
|
|
||||||
|
if(error != ErrorNumber.NoError)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
error = fs.Stat("VIDEO_TS", out FileEntryInfo stat);
|
||||||
|
fs.Unmount();
|
||||||
|
|
||||||
|
return error == ErrorNumber.NoError && stat.Attributes == FileAttributes.Directory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
108
DVD/MPEG.cs
Normal file
108
DVD/MPEG.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// /***************************************************************************
|
||||||
|
// Aaru Data Preservation Suite
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename : MPEG.cs
|
||||||
|
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
|
||||||
|
//
|
||||||
|
// --[ Description ] ----------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Handles MPEG packets functionality.
|
||||||
|
//
|
||||||
|
// --[ License ] --------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
// copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
// the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included
|
||||||
|
// in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
//
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright © 2023 Rebecca Wallander
|
||||||
|
// ****************************************************************************/
|
||||||
|
|
||||||
|
// http://www.mpucoder.com/DVD/vobov.html
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Aaru.Decryption.DVD;
|
||||||
|
|
||||||
|
public class MPEG
|
||||||
|
{
|
||||||
|
static byte[] _mpeg2PackHeaderStartCode =
|
||||||
|
{
|
||||||
|
0x0, 0x0, 0x1
|
||||||
|
};
|
||||||
|
|
||||||
|
public enum Mpeg2StreamId : byte
|
||||||
|
{
|
||||||
|
ProgramEnd = 0xB9, PackHeader = 0xBA, SystemHeader = 0xBB,
|
||||||
|
ProgramStreamMap = 0xBC, PrivateStream1 = 0xBD, PaddingStream = 0xBE,
|
||||||
|
PrivateStream2 = 0xBF, EcmStream = 0xF0, EmmStream = 0xF1,
|
||||||
|
ItuTRecH222_0_or_IsoIec13818_1AnnexA_or_IsoIec13818_6DsmccStream = 0xF2, IsoIec13522Stream = 0xF3,
|
||||||
|
ItuTRecH222_1TypeA = 0xF4, ItuTRecH222_1TypeB = 0xF5, ItuTRecH222_1TypeC = 0xF6,
|
||||||
|
ItuTRecH222_1TypeD = 0xF7, ItuTRecH222_1TypeE = 0xF8, AncillaryStream = 0xF9,
|
||||||
|
Reserved1 = 0xFA, Reserved2 = 0xFB, Reserved3 = 0xFC,
|
||||||
|
Reserved4 = 0xFD, Reserved5 = 0xFE, ProgramStreamDirectory = 0xFF,
|
||||||
|
|
||||||
|
// DVD Video can only hold 8 audio streams
|
||||||
|
MpegAudioStream1 = 0xC0, MpegAudioStream2 = 0xC1, MpegAudioStream3 = 0xC2,
|
||||||
|
MpegAudioStream4 = 0xC3, MpegAudioStream5 = 0xC4, MpegAudioStream6 = 0xC5,
|
||||||
|
MpegAudioStream7 = 0xC6, MpegAudioStream8 = 0xC7, MpegAudioStream9 = 0xC8,
|
||||||
|
MpegAudioStream10 = 0xC9, MpegAudioStream11 = 0xCA, MpegAudioStream12 = 0xCB,
|
||||||
|
MpegAudioStream13 = 0xCC, MpegAudioStream14 = 0xCD, MpegAudioStream15 = 0xCE,
|
||||||
|
MpegAudioStream16 = 0xCF, MpegAudioStream17 = 0xD0, MpegAudioStream18 = 0xD1,
|
||||||
|
MpegAudioStream19 = 0xD2, MpegAudioStream20 = 0xD3, MpegAudioStream21 = 0xD4,
|
||||||
|
MpegAudioStream22 = 0xD5, MpegAudioStream23 = 0xD6, MpegAudioStream24 = 0xD7,
|
||||||
|
MpegAudioStream25 = 0xD8, MpegAudioStream26 = 0xD9, MpegAudioStream27 = 0xDA,
|
||||||
|
MpegAudioStream28 = 0xDB, MpegAudioStream29 = 0xDC, MpegAudioStream30 = 0xDD,
|
||||||
|
MpegAudioStream31 = 0xDE, MpegAudioStream32 = 0xDF,
|
||||||
|
|
||||||
|
// DVD Video can only hold 1 video stream
|
||||||
|
MpegVideStream1 = 0xE0, MpegVideStream2 = 0xE1, MpegVideStream3 = 0xE2,
|
||||||
|
MpegVideStream4 = 0xE3, MpegVideStream5 = 0xE4, MpegVideStream6 = 0xE5,
|
||||||
|
MpegVideStream7 = 0xE6, MpegVideStream8 = 0xE7, MpegVideStream9 = 0xE8,
|
||||||
|
MpegVideStream10 = 0xE9, MpegVideStream11 = 0xEA, MpegVideStream12 = 0xEB,
|
||||||
|
MpegVideStream13 = 0xEC, MpegVideStream14 = 0xED, MpegVideStream15 = 0xEE,
|
||||||
|
MpegVideStream16 = 0xEF
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct MpegHeader
|
||||||
|
{
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||||
|
public byte[] startCode;
|
||||||
|
public byte packIdentifier;
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||||
|
public byte[] scrBlock;
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||||
|
public byte[] programMuxRateBlock;
|
||||||
|
byte packStuffingLengthBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ContainsMpegPackets(byte[] sectorData, uint blocks = 1, uint blockSize = 2048)
|
||||||
|
{
|
||||||
|
for(uint i = 0; i < blocks; i++)
|
||||||
|
if(IsMpegPacket(sectorData.Skip((int)(i * blockSize))))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsMpegPacket(IEnumerable<byte> sector) =>
|
||||||
|
sector.Take(3).ToArray().SequenceEqual(_mpeg2PackHeaderStartCode);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user