From cd2055a07a4657f4eb5ee7ef428ba5c69cc634e6 Mon Sep 17 00:00:00 2001 From: feyris-tan <4116042+feyris-tan@users.noreply.github.com> Date: Fri, 26 Dec 2025 14:17:53 +0100 Subject: [PATCH] Added support for loading from Softcam.key --- skyscraper8/Program.cs | 2 + .../AccessControl/AccessControlException.cs | 23 ++ .../Security/AccessControl/BissKey.cs | 42 ++++ .../Security/AccessControl/MultipartKey.cs | 13 ++ .../Security/AccessControl/NagravisionKey.cs | 43 ++++ .../Security/AccessControl/PowerVuKeyGroup.cs | 54 +++++ .../Security/AccessControl/SoftcamKeyset.cs | 217 ++++++++++++++++++ .../Security/AccessControl/TandbergKey.cs | 54 +++++ .../Security/AccessControl/ViaccessKey.cs | 78 +++++++ .../Security/Cryptography/DvbCsa2.cs | 9 +- .../Security/SkyscraperSecurityException.cs | 23 ++ skyscraper8/Skyscraper/StringExtensions.cs | 68 ++++++ 12 files changed, 618 insertions(+), 8 deletions(-) create mode 100644 skyscraper8/Skyscraper/Security/AccessControl/AccessControlException.cs create mode 100644 skyscraper8/Skyscraper/Security/AccessControl/BissKey.cs create mode 100644 skyscraper8/Skyscraper/Security/AccessControl/MultipartKey.cs create mode 100644 skyscraper8/Skyscraper/Security/AccessControl/NagravisionKey.cs create mode 100644 skyscraper8/Skyscraper/Security/AccessControl/PowerVuKeyGroup.cs create mode 100644 skyscraper8/Skyscraper/Security/AccessControl/SoftcamKeyset.cs create mode 100644 skyscraper8/Skyscraper/Security/AccessControl/TandbergKey.cs create mode 100644 skyscraper8/Skyscraper/Security/AccessControl/ViaccessKey.cs create mode 100644 skyscraper8/Skyscraper/Security/SkyscraperSecurityException.cs diff --git a/skyscraper8/Program.cs b/skyscraper8/Program.cs index 7a622d8..31c10a1 100644 --- a/skyscraper8/Program.cs +++ b/skyscraper8/Program.cs @@ -35,6 +35,7 @@ using skyscraper8.SatIp.RtspResponses; using skyscraper8.SimpleServiceDiscoveryProtocol; using skyscraper8.Skyscraper.Math; using skyscraper8.Skyscraper; +using skyscraper8.Skyscraper.Security.AccessControl; [assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config")] namespace skyscraper5 @@ -91,6 +92,7 @@ namespace skyscraper5 logger.DebugFormat("I'm a {0}-bit Process.", Environment.Is64BitProcess ? 64 : 32); PluginManager.GetInstance(); + SoftcamKeyset.GetInstance().InitializeFromFile(); if (args.Length != 0) { diff --git a/skyscraper8/Skyscraper/Security/AccessControl/AccessControlException.cs b/skyscraper8/Skyscraper/Security/AccessControl/AccessControlException.cs new file mode 100644 index 0000000..b066106 --- /dev/null +++ b/skyscraper8/Skyscraper/Security/AccessControl/AccessControlException.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.Skyscraper.Security.AccessControl +{ + public class AccessControlException : SkyscraperSecurityException + { + public AccessControlException() + { + } + + public AccessControlException(string message) : base(message) + { + } + + public AccessControlException(string message, Exception inner) : base(message, inner) + { + } + } +} diff --git a/skyscraper8/Skyscraper/Security/AccessControl/BissKey.cs b/skyscraper8/Skyscraper/Security/AccessControl/BissKey.cs new file mode 100644 index 0000000..515101c --- /dev/null +++ b/skyscraper8/Skyscraper/Security/AccessControl/BissKey.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.Skyscraper.Security.AccessControl +{ + internal class BissKey + { + public BissKey(uint bissCoordinate, byte[] keyData) + { + Coordinate = bissCoordinate; + Key = keyData; + } + + public byte[] Key { get; set; } + + public uint Coordinate { get; set; } + + protected bool Equals(BissKey other) + { + return Coordinate == other.Coordinate; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != this.GetType()) + return false; + return Equals((BissKey)obj); + } + + public override int GetHashCode() + { + return (int)Coordinate; + } + } +} diff --git a/skyscraper8/Skyscraper/Security/AccessControl/MultipartKey.cs b/skyscraper8/Skyscraper/Security/AccessControl/MultipartKey.cs new file mode 100644 index 0000000..5c433d9 --- /dev/null +++ b/skyscraper8/Skyscraper/Security/AccessControl/MultipartKey.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.Skyscraper.Security.AccessControl +{ + internal interface MultipartKey + { + bool SetHint(string keyType, byte[] key); + } +} diff --git a/skyscraper8/Skyscraper/Security/AccessControl/NagravisionKey.cs b/skyscraper8/Skyscraper/Security/AccessControl/NagravisionKey.cs new file mode 100644 index 0000000..18caf49 --- /dev/null +++ b/skyscraper8/Skyscraper/Security/AccessControl/NagravisionKey.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.Skyscraper.Security.AccessControl +{ + internal class NagravisionKey : MultipartKey + { + public NagravisionKey(uint nagravisionGroupid) + { + Group = nagravisionGroupid; + } + + public uint Group { get; private set; } + + public bool SetHint(string keyType, byte[] key) + { + switch (keyType) + { + case "00": + KeyA = key; + return true; + case "01": + KeyB = key; + return true; + case "M1": + Master = key; + return true; + default: + return false; + + } + } + + public byte[] Master { get; set; } + + public byte[] KeyB { get; set; } + + public byte[] KeyA { get; set; } + } +} diff --git a/skyscraper8/Skyscraper/Security/AccessControl/PowerVuKeyGroup.cs b/skyscraper8/Skyscraper/Security/AccessControl/PowerVuKeyGroup.cs new file mode 100644 index 0000000..e599d16 --- /dev/null +++ b/skyscraper8/Skyscraper/Security/AccessControl/PowerVuKeyGroup.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.Skyscraper.Security.AccessControl +{ + internal class PowerVuKeyGroup + { + public uint Coordinate { get; } + + public PowerVuKeyGroup(uint coordinate, ushort groupId) + { + Coordinate = coordinate; + GroupId = groupId; + } + + public ushort GroupId { get; set; } + + public bool SetHint(uint coordinate, uint keyType, byte[] key) + { + if ((coordinate & 0x0000ffff) == 0x0000ffff) + { + if (keyType == 0) + { + EcmA = key; + return true; + } + else if (keyType == 1) + { + EcmB = key; + return true; + } + else + { + return false; + } + } + if (_keys == null) + _keys = new List>(); + + _keys.Add(new Tuple(coordinate, keyType, key)); + return true; + } + + public byte[] EcmB { get; set; } + + public byte[] EcmA { get; set; } + + private List> _keys; + public IReadOnlyList> Keys; + } +} diff --git a/skyscraper8/Skyscraper/Security/AccessControl/SoftcamKeyset.cs b/skyscraper8/Skyscraper/Security/AccessControl/SoftcamKeyset.cs new file mode 100644 index 0000000..2d6069f --- /dev/null +++ b/skyscraper8/Skyscraper/Security/AccessControl/SoftcamKeyset.cs @@ -0,0 +1,217 @@ +using log4net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; + +namespace skyscraper8.Skyscraper.Security.AccessControl +{ + internal class SoftcamKeyset + { + private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name); + private static SoftcamKeyset _singleton; + public static SoftcamKeyset GetInstance() + { + if (_singleton == null) + { + _singleton = new SoftcamKeyset(); + } + return _singleton; + } + + private FileInfo TryFindSoftcamKey() + { + foreach (FileInfo fi in GetPossibleLocations()) + { + if (fi.Exists) + { + return fi; + } + } + + return null; + } + + private const string FILE_NAME = "SoftCam.Key"; + private IEnumerable GetPossibleLocations() + { + FileInfo self = new FileInfo(FILE_NAME); + yield return self; + + string assemblyLocation = this.GetType().Assembly.Location; + FileInfo assemblyLocationFileInfo = new FileInfo(assemblyLocation); + DirectoryInfo assemblyLocationDirectory = assemblyLocationFileInfo.Directory; + string assemblyLocationKeyfileName = Path.Combine(assemblyLocationDirectory.FullName, FILE_NAME); + yield return new FileInfo(assemblyLocationKeyfileName); + + DirectoryInfo rootDirectory = self.Directory.Root; + DirectoryInfo selfDirectory = self.Directory; + do + { + selfDirectory = selfDirectory.Parent; + if (selfDirectory == null) + break; + string possibleName = Path.Combine(selfDirectory.FullName, FILE_NAME); + yield return new FileInfo(possibleName); + } while (!selfDirectory.Equals(rootDirectory)); + + yield return new FileInfo("C:\\FT\\SoftCam_Emu\\SoftCam.Key"); + } + + private int LoadSoftcamKey(FileInfo fi) + { + int result = 0; + StreamReader streamReader = fi.OpenText(); + PowerVuKeyGroup currentPowerVuKeyGroup = null; + while (!streamReader.EndOfStream) + { + string line = streamReader.ReadLine(); + line = line.Trim(); + if (line.Contains(";")) + { + int indexOf = line.IndexOf(";"); + line = line.Substring(0, indexOf); + } + if (string.IsNullOrEmpty(line)) + continue; + + char keyType = line[0]; + string[] split = line.Split(' '); + byte[] keyData = null; + switch (keyType) + { + case '#': + case ';': //comment + break; + case '-': //unknown, perhaps seperator? + break; + case 'V': //Viaccess + uint viaccessGroup = split[1].HexToUint32BE(); + string viaccessKeyType = split[2]; + keyData = split[3].HexToBytes(); + + if (_viaccessKeys == null) + _viaccessKeys = new List(); + ViaccessKey viaccessKey = _viaccessKeys.FirstOrDefault(x => x.Group == viaccessGroup); + if (viaccessKey == null) + { + viaccessKey = new ViaccessKey(viaccessGroup); + _viaccessKeys.Add(viaccessKey); + } + + if (viaccessKey.SetHint(viaccessKeyType, keyData)) + result++; + break; + case 'N': + uint nagravisionGroupid = split[1].HexToUint32BE(); + string nagravisionKeyType = split[2]; + keyData = split[3].HexToBytes(); + + if (_nagravisionKeys == null) + _nagravisionKeys = new List(); + NagravisionKey nagravisionKey = _nagravisionKeys.FirstOrDefault(x => x.Group == nagravisionGroupid); + if (nagravisionKey == null) + { + nagravisionKey = new NagravisionKey(nagravisionGroupid); + _nagravisionKeys.Add(nagravisionKey); + } + + if (nagravisionKey.SetHint(nagravisionKeyType, keyData)) + result++; + break; + case 'T': + ushort tandbergChannelId = split[1].HexToUInt16BE(); + byte tandbergStreamId = split[2].HexToUInt8(); + keyData = split[3].HexToBytes(); + + TandbergKey tandbergKey = new TandbergKey(tandbergChannelId, tandbergStreamId, keyData); + if (tandbergKey.Valid) + { + if (_tandbergKeys == null) + _tandbergKeys = new HashSet(); + _tandbergKeys.Add(tandbergKey); + result++; + } + break; + case 'P': + case 'p': + uint powerVuKeyCoordinate = split[1].HexToUint32BE(); + string powerVuKeyType = split[2]; + if (powerVuKeyType.Equals("GROUP")) + { + ushort groupId = split[3].HexToUInt16BE(); + PowerVuKeyGroup powerVuGroup = new PowerVuKeyGroup(powerVuKeyCoordinate, groupId); + currentPowerVuKeyGroup = powerVuGroup; + if (_powerVuGroups == null) + _powerVuGroups = new List(); + _powerVuGroups.Add(currentPowerVuKeyGroup); + } + else + { + keyData = split[3].HexToBytes(); + currentPowerVuKeyGroup.SetHint(powerVuKeyCoordinate, powerVuKeyType.HexToUint32LE(), keyData); + result++; + } + break; + case 'F': + uint bissCoordinate = split[1].HexToUint32BE(); + uint bissFixed = split[2].HexToUint32BE(); + keyData = split[3].HexToBytes(); + if (bissFixed != 0) + { + logger.ErrorFormat("I don't understand this key: {0}", line); + break; + } + + if (_bissKeys == null) + _bissKeys = new HashSet(); + if (_bissKeys.Add(new BissKey(bissCoordinate, keyData))) + result++; + else + logger.ErrorFormat("Duplicate BISS Key for {0}", split[1]); + break; + default: + logger.ErrorFormat("Unknown Key Type \"{0}\". Abort loading key set.",keyType); + return result; + } + } + + return result; + } + + private bool doneFileInitalization; + public void InitializeFromFile() + { + if (doneFileInitalization) + { + throw new AccessControlException("Softcam.key Keyset already initalized."); + } + + doneFileInitalization = true; + FileInfo keyLocation = TryFindSoftcamKey(); + if (keyLocation != null) + { + logger.InfoFormat("Loading keys from: {0}", keyLocation.FullName); + int numKeysLoaded = LoadSoftcamKey(keyLocation); + logger.InfoFormat("I suppose you are legally entitled to use those {0} keys I just loaded, You are, right?",numKeysLoaded); + } + } + + private List _viaccessKeys; + public IReadOnlyList ViaccessKeys => _viaccessKeys; + + private List _nagravisionKeys; + public IReadOnlyList NagravisionKeys => _nagravisionKeys; + + private HashSet _tandbergKeys; + public IReadOnlySet TandbergKeys => _tandbergKeys; + + private List _powerVuGroups; + public IReadOnlyList PowerVuGroups => _powerVuGroups; + + private HashSet _bissKeys; + public IReadOnlySet BissKeys => _bissKeys; + } +} diff --git a/skyscraper8/Skyscraper/Security/AccessControl/TandbergKey.cs b/skyscraper8/Skyscraper/Security/AccessControl/TandbergKey.cs new file mode 100644 index 0000000..8761425 --- /dev/null +++ b/skyscraper8/Skyscraper/Security/AccessControl/TandbergKey.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; + +namespace skyscraper8.Skyscraper.Security.AccessControl +{ + internal class TandbergKey : Validatable + { + public ushort ChannelId { get; } + public byte StreamId { get; } + public byte[] Key { get; } + + public TandbergKey(ushort channelId, byte streamId, byte[] key) + { + ChannelId = channelId; + StreamId = streamId; + Key = key; + + if (key == null) + Valid = false; + else if (key.Length != 8) + Valid = false; + else + Valid = true; + } + + protected bool Equals(TandbergKey other) + { + ulong ourKey = BitConverter.ToUInt64(Key, 0); + ulong theirKey = BitConverter.ToUInt64(other.Key, 0); + return ChannelId == other.ChannelId && StreamId == other.StreamId && ourKey == theirKey; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != this.GetType()) + return false; + return Equals((TandbergKey)obj); + } + + public override int GetHashCode() + { + ulong numericKey = BitConverter.ToUInt64(Key, 0); + return HashCode.Combine(ChannelId, StreamId, numericKey); + } + } +} diff --git a/skyscraper8/Skyscraper/Security/AccessControl/ViaccessKey.cs b/skyscraper8/Skyscraper/Security/AccessControl/ViaccessKey.cs new file mode 100644 index 0000000..84e7e2a --- /dev/null +++ b/skyscraper8/Skyscraper/Security/AccessControl/ViaccessKey.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; + +namespace skyscraper8.Skyscraper.Security.AccessControl +{ + internal class ViaccessKey : MultipartKey + { + public ViaccessKey(uint viaccessGroup) + { + Group = viaccessGroup; + } + + public uint Group { get; set; } + + public bool SetHint(string viaccessKeyType, byte[] keyData) + { + viaccessKeyType = viaccessKeyType.ToUpper(); + switch (viaccessKeyType) + { + case "00": + case "01": + case "02": + case "03": + case "04": + case "05": + case "06": + case "07": + case "08": + case "09": + case "0A": + case "0B": + case "0C": + case "0D": + case "0E": + case "0F": + uint keyNUmber = viaccessKeyType.HexToUint32BE(); + keyNUmber >>= 24; + if (Keyset == null) + Keyset = new byte[16][]; + Keyset[keyNUmber] = keyData; + return true; + case "D1": + Des1Key = keyData; + return true; + case "X1": + XorArray = keyData; + return true; + case "P1": + PermArray = keyData; + return true; + case "C1": + ChainArray = keyData; + return true; + case "T1": + TransformTable = keyData; + return true; + default: + return false; + } + } + + public byte[] TransformTable { get; set; } + + public byte[] ChainArray { get; set; } + + public byte[] PermArray { get; set; } + + public byte[] XorArray { get; set; } + + public byte[] Des1Key { get; set; } + + public byte[][] Keyset { get; private set; } + } +} diff --git a/skyscraper8/Skyscraper/Security/Cryptography/DvbCsa2.cs b/skyscraper8/Skyscraper/Security/Cryptography/DvbCsa2.cs index 283a74f..43ee0ad 100644 --- a/skyscraper8/Skyscraper/Security/Cryptography/DvbCsa2.cs +++ b/skyscraper8/Skyscraper/Security/Cryptography/DvbCsa2.cs @@ -24,7 +24,6 @@ namespace skyscraper8.Skyscraper.Security.Cryptography public DvbCsa2(EntropyMode mode = EntropyMode.REDUCE_ENTROPY) { SetEntropyMode(mode); - canProcessInPlace(true); } static readonly int[] sbox1 = { @@ -75,13 +74,7 @@ namespace skyscraper8.Skyscraper.Security.Cryptography 1,0,3,3,0,1,1,2, 2,3,1,0,2,3,0,2 }; - - - private void canProcessInPlace(bool b) - { - throw new NotImplementedException(); - } - + public void SetEntropyMode(EntropyMode mode) { _mode = mode; diff --git a/skyscraper8/Skyscraper/Security/SkyscraperSecurityException.cs b/skyscraper8/Skyscraper/Security/SkyscraperSecurityException.cs new file mode 100644 index 0000000..b469be3 --- /dev/null +++ b/skyscraper8/Skyscraper/Security/SkyscraperSecurityException.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.Skyscraper.Security +{ + public class SkyscraperSecurityException : SkyscraperCoreException + { + public SkyscraperSecurityException() + { + } + + public SkyscraperSecurityException(string message) : base(message) + { + } + + public SkyscraperSecurityException(string message, Exception inner) : base(message, inner) + { + } + } +} diff --git a/skyscraper8/Skyscraper/StringExtensions.cs b/skyscraper8/Skyscraper/StringExtensions.cs index 31b0752..7d0c8f4 100644 --- a/skyscraper8/Skyscraper/StringExtensions.cs +++ b/skyscraper8/Skyscraper/StringExtensions.cs @@ -37,5 +37,73 @@ namespace skyscraper5.Skyscraper return new string(charArray); } + + public static byte[] HexToBytes(this string hex) + { + return Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); + } + + public static uint HexToUint32BE(this string hex) + { + byte[] hexToBytes = hex.HexToBytes(); + if (hexToBytes.Length < 4) + { + byte[] better = new byte[4]; + Array.Copy(hexToBytes, 0, better, 0, hexToBytes.Length); + hexToBytes = better; + } + + (hexToBytes[3], hexToBytes[2], hexToBytes[1], hexToBytes[0]) = (hexToBytes[0], hexToBytes[1], hexToBytes[2], hexToBytes[3]); + return BitConverter.ToUInt32(hexToBytes, 0); + } + + public static uint HexToUint32LE(this string hex) + { + byte[] hexToBytes = hex.HexToBytes(); + if (hexToBytes.Length < 4) + { + byte[] better = new byte[4]; + Array.Copy(hexToBytes, 0, better, 0, hexToBytes.Length); + hexToBytes = better; + } + + return BitConverter.ToUInt32(hexToBytes, 0); + } + + public static ushort HexToUInt16LE(this string hex) + { + byte[] hexToBytes = hex.HexToBytes(); + if (hexToBytes.Length < 2) + { + byte[] better = new byte[2]; + Array.Copy(hexToBytes, 0, better, 0, hexToBytes.Length); + hexToBytes = better; + } + + return BitConverter.ToUInt16(hexToBytes, 0); + } + + public static ushort HexToUInt16BE(this string hex) + { + byte[] hexToBytes = hex.HexToBytes(); + if (hexToBytes.Length < 2) + { + byte[] better = new byte[2]; + Array.Copy(hexToBytes, 0, better, 0, hexToBytes.Length); + hexToBytes = better; + } + + (hexToBytes[1], hexToBytes[0]) = (hexToBytes[0], hexToBytes[1]); + return BitConverter.ToUInt16(hexToBytes, 0); + } + + public static byte HexToUInt8(this string hex) + { + byte[] hexToBytes = hex.HexToBytes(); + return hexToBytes[0]; + } } }