diff --git a/UnlockECU/UnlockECU/Security/KI203Algo1.cs b/UnlockECU/UnlockECU/Security/KI203Algo1.cs new file mode 100644 index 0000000..3ba2438 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/KI203Algo1.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; + +namespace UnlockECU +{ + /// + /// KI203Algo1 : https://github.com/jglim/UnlockECU/issues/30 + /// Collaborative effort by @rumator, Sergey (@Feezex) and Vladyslav Lupashevskyi (@VladLupashevskyi) + /// This implementation is the original variant as reversed by @VladLupashevskyi using a firmware dump from @rumator, + /// and it is preferred as the root keys can be directly used from a firmware dump. + /// The root keys are often found in the firmware as a 7-element array of 32-bit integers, one for each level + /// https://github.com/jglim/UnlockECU/issues/30#issuecomment-1881151971 + /// + class KI203Algo1 : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + if ((inSeed.Length != 4) || (outKey.Length != 4)) + { + return false; + } + + uint root = BytesToInt(GetParameterBytearray(parameters, "K"), Endian.Big); + int ones = CountOnes(root); + + uint val = (uint)( + (inSeed[2] << 0) | + (inSeed[0] << 8) | + (inSeed[3] << 16) | + (inSeed[1] << 24) + ); + + val = RotateLeft(val, 3); + val ^= root; + val = RotateRight(val, ones); + + outKey[0] = GetByte(val, 0); + outKey[1] = GetByte(val, 2); + outKey[2] = GetByte(val, 3); + outKey[3] = GetByte(val, 1); + return true; + } + + /// + /// Condensed version of the original algo by @Feezex, where the shifts and transpositions + /// have been folded into the root key, and only a single transpose op is applied once before the xor + /// This implementation is here for reference and is not directly used by the application + /// + private static void CondensedGenerateKey() + { + byte[] input = new byte[] { 0x3B, 0x1C, 0x0D, 0xDD }; + byte[] root = new byte[] { 0xF8, 0x20, 0x5A, 0x4F }; + + byte[] result = new byte[4] + { + (byte)(((input[3] & 0xF) << 4) | (input[0] >> 4)), + (byte)(((input[2] & 0xF) << 4) | (input[1] >> 4)), + (byte)(((input[0] & 0xF) << 4) | (input[2] >> 4)), + (byte)(((input[1] & 0xF) << 4) | (input[3] >> 4)) + }; + + for (int i = 0; i < root.Length; i++) + { + result[i] ^= root[i]; + } + // Expects result to be 2BF1EA82 + } + + /// + /// Converts a condensed key into a original key + /// This implementation is here for reference and is not directly used by the application + /// + static uint ConvertCondensedKeyToOriginal(uint val) + { + // Shared by @Feezex in https://github.com/jglim/UnlockECU/issues/30#issuecomment-1881815230 + // 203XXXXXXX_0223: 0x758A9A61 -> 30BACD45 + // 203XXXXXXX_0287: 0xF8205A4F-> 27FC2D10 + // 203XXXXXXX_0290: 0x054EDE92-> 4902EF27 + uint prekey = + (0xFF00_0000 & (val << 16)) | + (0x00FF_0000 & (val)) | + (0x0000_FF00 & (val << 8)) | + (0x0000_00FF & (val >> 24)); + return RotateLeft(prekey, CountOnes(prekey)); + } + + public override string GetProviderName() + { + return "KI203Algo1"; + } + } +} + diff --git a/UnlockECU/UnlockECU/Security/SecurityProvider.cs b/UnlockECU/UnlockECU/Security/SecurityProvider.cs index 7388946..401e84b 100644 --- a/UnlockECU/UnlockECU/Security/SecurityProvider.cs +++ b/UnlockECU/UnlockECU/Security/SecurityProvider.cs @@ -1,202 +1,245 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace UnlockECU -{ - /// - /// Basic SecurityProvider to be inherited from. This class should not be directly initialized. - /// - public class SecurityProvider - { - public virtual string GetProviderName() - { - return "ProviderName was not initialized"; - } - - public virtual bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) - { - throw new Exception("GenerateKey was not overridden"); - } - - public static byte GetParameterByte(List parameters, string key) - { - foreach (Parameter row in parameters) - { - if ((row.Key == key) && (row.DataType == "Byte")) - { - return (byte)(int.Parse(row.Value, System.Globalization.NumberStyles.HexNumber)); - } - } - throw new Exception($"Failed to fetch byte parameter for key: {key}"); - } - public static int GetParameterInteger(List parameters, string key) - { - foreach (Parameter row in parameters) - { - if ((row.Key == key) && (row.DataType == "Int32")) - { - return int.Parse(row.Value, System.Globalization.NumberStyles.HexNumber); - } - } - throw new Exception($"Failed to fetch Int32 parameter for key: {key}"); - } - public static long GetParameterLong(List parameters, string key) - { - foreach (Parameter row in parameters) - { - if ((row.Key == key) && (row.DataType == "Int64")) - { - return long.Parse(row.Value, System.Globalization.NumberStyles.HexNumber); - } - } - throw new Exception($"Failed to fetch Int64 parameter for key: {key}"); - } - public static byte[] GetParameterBytearray(List parameters, string key) - { - foreach (Parameter row in parameters) - { - if ((row.Key == key) && (row.DataType == "ByteArray")) - { - return BitUtility.BytesFromHex(row.Value); - } - } - throw new Exception($"Failed to fetch ByteArray parameter for key: {key}"); - } - - private static bool IsInitialized = false; - private static List SecurityProviders = new(); - - public static List GetSecurityProviders() - { - if (IsInitialized) - { - return SecurityProviders; - } - SecurityProviders = new List(); - - System.Reflection.Assembly - .GetExecutingAssembly() - .GetTypes() - .Where(x => x.IsSubclassOf(typeof(SecurityProvider))) - .ToList() - .ForEach(x => SecurityProviders.Add((SecurityProvider)Activator.CreateInstance(x))); - IsInitialized = true; - - return SecurityProviders; - } - - public enum Endian - { - Big, - Little, - } - - public static uint BytesToInt(byte[] inBytes, Endian endian, int offset = 0) - { - uint result = 0; - if (endian == Endian.Big) - { - result |= (uint)inBytes[offset++] << 24; - result |= (uint)inBytes[offset++] << 16; - result |= (uint)inBytes[offset++] << 8; - result |= (uint)inBytes[offset++] << 0; - } - else - { - result |= (uint)inBytes[offset++] << 0; - result |= (uint)inBytes[offset++] << 8; - result |= (uint)inBytes[offset++] << 16; - result |= (uint)inBytes[offset++] << 24; - } - return result; - } - public static void IntToBytes(uint inInt, byte[] outBytes, Endian endian) - { - if (endian == Endian.Big) - { - outBytes[0] = (byte)(inInt >> 24); - outBytes[1] = (byte)(inInt >> 16); - outBytes[2] = (byte)(inInt >> 8); - outBytes[3] = (byte)(inInt >> 0); - } - else - { - outBytes[3] = (byte)(inInt >> 24); - outBytes[2] = (byte)(inInt >> 16); - outBytes[1] = (byte)(inInt >> 8); - outBytes[0] = (byte)(inInt >> 0); - } - } - - // WARNING: endian unaware: - public static byte GetBit(byte inByte, int bitPosition) - { - if (bitPosition > 7) - { - throw new Exception("Attempted to shift beyond 8 bits in a byte"); - } - return (byte)((inByte >> bitPosition) & 1); - } - - public static byte GetByte(uint inInt, int bytePosition) - { - if (bytePosition > 3) - { - throw new Exception("Attempted to shift beyond 4 bytes in an uint"); - } - return (byte)(inInt >> (8 * bytePosition)); - } - - public static byte SetBit(byte inByte, int bitPosition) - { - if (bitPosition > 7) - { - throw new Exception("Attempted to shift beyond 8 bits in a byte"); - } - return inByte |= (byte)(1 << bitPosition); - } - - public static uint SetByte(uint inInt, byte byteToSet, int bytePosition) - { - if (bytePosition > 3) - { - throw new Exception("Attempted to shift beyond 4 bytes in an uint"); - } - int bitPosition = 8 * bytePosition; - inInt &= ~(uint)(0xFF << bitPosition); - inInt |= (uint)(byteToSet << bitPosition); - return inInt; - } - - public static byte[] ExpandByteArrayToNibbles(byte[] inputArray) - { - // Primarily used for IC172 - byte[] result = new byte[inputArray.Length * 2]; - for (int i = 0; i < inputArray.Length; i++) - { - result[i * 2] = (byte)((inputArray[i] >> 4) & 0xF); - result[i * 2 + 1] = (byte)(inputArray[i] & 0xF); - } - return result; - } - - public static byte[] CollapseByteArrayFromNibbles(byte[] inputArray) - { - // Primarily used for IC172 - if ((inputArray.Length % 2) != 0) - { - throw new Exception("Attempted to form a byte array from an odd-numbered set of nibbles."); - } - byte[] result = new byte[inputArray.Length / 2]; - for (int i = 0; i < result.Length; i++) - { - result[i] = (byte)((inputArray[i * 2] << 4) | (inputArray[i * 2 + 1])); - } - return result; - } - - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Basic SecurityProvider to be inherited from. This class should not be directly initialized. + /// + public class SecurityProvider + { + public virtual string GetProviderName() + { + return "ProviderName was not initialized"; + } + + public virtual bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + throw new Exception("GenerateKey was not overridden"); + } + + public static byte GetParameterByte(List parameters, string key) + { + foreach (Parameter row in parameters) + { + if ((row.Key == key) && (row.DataType == "Byte")) + { + return (byte)(int.Parse(row.Value, System.Globalization.NumberStyles.HexNumber)); + } + } + throw new Exception($"Failed to fetch byte parameter for key: {key}"); + } + public static int GetParameterInteger(List parameters, string key) + { + foreach (Parameter row in parameters) + { + if ((row.Key == key) && (row.DataType == "Int32")) + { + return int.Parse(row.Value, System.Globalization.NumberStyles.HexNumber); + } + } + throw new Exception($"Failed to fetch Int32 parameter for key: {key}"); + } + public static long GetParameterLong(List parameters, string key) + { + foreach (Parameter row in parameters) + { + if ((row.Key == key) && (row.DataType == "Int64")) + { + return long.Parse(row.Value, System.Globalization.NumberStyles.HexNumber); + } + } + throw new Exception($"Failed to fetch Int64 parameter for key: {key}"); + } + public static byte[] GetParameterBytearray(List parameters, string key) + { + foreach (Parameter row in parameters) + { + if ((row.Key == key) && (row.DataType == "ByteArray")) + { + return BitUtility.BytesFromHex(row.Value); + } + } + throw new Exception($"Failed to fetch ByteArray parameter for key: {key}"); + } + + private static bool IsInitialized = false; + private static List SecurityProviders = new(); + + public static List GetSecurityProviders() + { + if (IsInitialized) + { + return SecurityProviders; + } + SecurityProviders = new List(); + + System.Reflection.Assembly + .GetExecutingAssembly() + .GetTypes() + .Where(x => x.IsSubclassOf(typeof(SecurityProvider))) + .ToList() + .ForEach(x => SecurityProviders.Add((SecurityProvider)Activator.CreateInstance(x))); + IsInitialized = true; + + return SecurityProviders; + } + + public enum Endian + { + Big, + Little, + } + + public static uint BytesToInt(byte[] inBytes, Endian endian, int offset = 0) + { + uint result = 0; + if (endian == Endian.Big) + { + result |= (uint)inBytes[offset++] << 24; + result |= (uint)inBytes[offset++] << 16; + result |= (uint)inBytes[offset++] << 8; + result |= (uint)inBytes[offset++] << 0; + } + else + { + result |= (uint)inBytes[offset++] << 0; + result |= (uint)inBytes[offset++] << 8; + result |= (uint)inBytes[offset++] << 16; + result |= (uint)inBytes[offset++] << 24; + } + return result; + } + public static void IntToBytes(uint inInt, byte[] outBytes, Endian endian) + { + if (endian == Endian.Big) + { + outBytes[0] = (byte)(inInt >> 24); + outBytes[1] = (byte)(inInt >> 16); + outBytes[2] = (byte)(inInt >> 8); + outBytes[3] = (byte)(inInt >> 0); + } + else + { + outBytes[3] = (byte)(inInt >> 24); + outBytes[2] = (byte)(inInt >> 16); + outBytes[1] = (byte)(inInt >> 8); + outBytes[0] = (byte)(inInt >> 0); + } + } + + // WARNING: endian unaware: + public static byte GetBit(byte inByte, int bitPosition) + { + if (bitPosition > 7) + { + throw new Exception("Attempted to shift beyond 8 bits in a byte"); + } + return (byte)((inByte >> bitPosition) & 1); + } + + public static byte GetByte(uint inInt, int bytePosition) + { + if (bytePosition > 3) + { + throw new Exception("Attempted to shift beyond 4 bytes in an uint"); + } + return (byte)(inInt >> (8 * bytePosition)); + } + + public static byte SetBit(byte inByte, int bitPosition) + { + if (bitPosition > 7) + { + throw new Exception("Attempted to shift beyond 8 bits in a byte"); + } + return inByte |= (byte)(1 << bitPosition); + } + + public static uint SetByte(uint inInt, byte byteToSet, int bytePosition) + { + if (bytePosition > 3) + { + throw new Exception("Attempted to shift beyond 4 bytes in an uint"); + } + int bitPosition = 8 * bytePosition; + inInt &= ~(uint)(0xFF << bitPosition); + inInt |= (uint)(byteToSet << bitPosition); + return inInt; + } + + public static byte[] ExpandByteArrayToNibbles(byte[] inputArray) + { + // Primarily used for IC172 + byte[] result = new byte[inputArray.Length * 2]; + for (int i = 0; i < inputArray.Length; i++) + { + result[i * 2] = (byte)((inputArray[i] >> 4) & 0xF); + result[i * 2 + 1] = (byte)(inputArray[i] & 0xF); + } + return result; + } + + public static byte[] CollapseByteArrayFromNibbles(byte[] inputArray) + { + // Primarily used for IC172 + if ((inputArray.Length % 2) != 0) + { + throw new Exception("Attempted to form a byte array from an odd-numbered set of nibbles."); + } + byte[] result = new byte[inputArray.Length / 2]; + for (int i = 0; i < result.Length; i++) + { + result[i] = (byte)((inputArray[i * 2] << 4) | (inputArray[i * 2 + 1])); + } + return result; + } + + public static uint RotateLeft(uint val, int count) + { + uint mask = ~(uint.MaxValue >> 1); + for (int i = 0; i < count; i++) + { + bool bitSet = (val & mask) > 0; + val <<= 1; + if (bitSet) + { + val |= 1; + } + } + return val; + } + + public static uint RotateRight(uint val, int count) + { + uint mask = ~(uint.MaxValue >> 1); + for (int i = 0; i < count; i++) + { + bool bitSet = (val & 1) > 0; + val >>= 1; + if (bitSet) + { + val |= mask; + } + } + return val; + } + + public static int CountOnes(uint val) + { + int result = 0; + while (val > 0) + { + if ((val & 1) > 0) + { + result++; + } + val >>= 1; + } + return result; + } + } +} diff --git a/UnlockECU/db.json b/UnlockECU/db.json index b026412..50819fe 100644 --- a/UnlockECU/db.json +++ b/UnlockECU/db.json @@ -5274,6 +5274,54 @@ } ] }, + { + "EcuName": "KI203", + "Aliases": [], + "AccessLevel": 1, + "SeedLength": 4, + "KeyLength": 4, + "Provider": "KI203Algo1", + "Origin": "KI203_203_0223_L7_@Feezex-@rumator-@VladLupashevskyi", + "Parameters": [ + { + "Key": "K", + "Value": "30BACD45", + "DataType": "ByteArray" + } + ] + }, + { + "EcuName": "KI203", + "Aliases": [], + "AccessLevel": 1, + "SeedLength": 4, + "KeyLength": 4, + "Provider": "KI203Algo1", + "Origin": "KI203_203_0287_L7_@Feezex-@rumator-@VladLupashevskyi", + "Parameters": [ + { + "Key": "K", + "Value": "27FC2D10", + "DataType": "ByteArray" + } + ] + }, + { + "EcuName": "KI203", + "Aliases": [], + "AccessLevel": 1, + "SeedLength": 4, + "KeyLength": 4, + "Provider": "KI203Algo1", + "Origin": "KI203_203_0290_L7_@Feezex-@rumator-@VladLupashevskyi", + "Parameters": [ + { + "Key": "K", + "Value": "4902EF27", + "DataType": "ByteArray" + } + ] + }, { "EcuName": "KI211", "Aliases": [],