diff --git a/README.md b/README.md new file mode 100644 index 0000000..9a4f688 --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ +# UnlockECU + +![Header Image](https://raw.githubusercontent.com/jglim/UnlockECU/main/docs/resources/header.png) + +Free, open-source ECU seed-key unlocking tool. + +## Getting started + +Download and unarchive the application from the [Releases](https://github.com/jglim/UnlockECU/releases/) page, then run the main application `VisualUnlockECU.exe`. + +Ensure that you have *.NET Desktop Runtime 5.0.0*. , available from [here](https://dotnet.microsoft.com/download/dotnet/5.0). + +## License + +MIT + +Icon from [http://www.famfamfam.com/lab/icons/silk/](http://www.famfamfam.com/lab/icons/silk/) + +Excluding the icon, this application **does not include or require copyrighted or proprietary files**. Security functions and definitions have been reverse-engineered and reimplemented. + +*When interacting with this repository (PR, issues, comments), please avoid including copyrighted/proprietary files, as they will be removed without notice.* + +## Features + +- There is no need for additional files such as security DLLs. The application supports a set of security providers out of the box, and definitions are stored in `db.json`. +- Security functions are completely reverse engineered and re-implemented in C#. +- The project is unencumbered by proprietary binary blobs, and can be shared freely without legal issues. + +## Demo + +![https://raw.githubusercontent.com/jglim/UnlockECU/main/docs/resources/demo.mp4](https://raw.githubusercontent.com/jglim/UnlockECU/main/docs/resources/demo-thumb.png) + +## Adding definitions + +Definitions specify a seed-key function for a specific ECU and security level. The input seed's size, output key's length as well as the security provider must be specified. Some security providers require specific parameters to operate. + +Here is an example of a definition: + +``` +{ + "EcuName": "ME97", + "AccessLevel": 5, + "SeedLength": 2, + "KeyLength": 2, + "Provider": "PowertrainBoschContiSecurityAlgo2", + "Origin": "ME97_ME97_13_10_01", + "Parameters": [ + { + "Key": "Table", + "Value": "37C1A8179AE3745B", + "DataType": "ByteArray" + }, + { + "Key": "uwMasc", + "Value": "4108", + "DataType": "ByteArray" + } + ] + } +``` + +Currently, these security providers are available: + +- DaimlerStandardSecurityAlgo +- DaimlerStandardSecurityAlgoMod +- DaimlerStandardSecurityAlgoRefG +- DRVU_PROF +- EDIFF290 +- EsLibEd25519 +- ESPSecurityAlgoLevel1 +- MarquardtSecurityAlgo +- OCM172 +- PowertrainBoschContiSecurityAlgo1 +- PowertrainBoschContiSecurityAlgo2 +- PowertrainDelphiSecurityAlgo +- PowertrainSecurityAlgo +- PowertrainSecurityAlgo2 +- PowertrainSecurityAlgoNFZ +- RBTM +- RDU222 +- RVC222_MPC222_FCW246_LRR3 +- SWSP177 + +The definitions file `db.json` should be found alongside the application's main binary. + +## Notes + +- If your diagnostics file has unlocking capabilities, usually your diagnostics client can already perform the unlocking without further aid. Check your client's available functions for phrases such as `Entriegeln` , `Zugriffberechtigung` , and `Unlock`. +- Generally, this application operates like most DLL-based seed-key generators. If you already have a DLL-based tool, this application does not offer much more (only includes a few modern targets such as `HU7`). +- Definitions are reverse-engineered from DLLs and SMR-D files. If the definition does not innately exist in those files, they will not be available here (e.g. high-level instrument cluster definitions). +- There are ECUs that share the same seed-key function. For example, `CRD3` and `CRD3S2` appear to share the same function as `CRD3NFZ`. +- The core of this project is a "portable" .NET 5 class library which can be reused on other platforms. +- As the security providers are now written in a high-level language, they can be better studied. For example, `DaimlerStandardSecurityAlgo` performs a XOR with its private key as a final step, which allows the private key to be recovered from a known seed and key. +- `DaimlerStandardSecurityAlgo` is usually used for firmware flashing, and might not unlock other capabilities such as variant-coding. + +## Contributing + +Contributions in adding security providers and definitions are welcome. \ No newline at end of file diff --git a/UnlockECU/UnlockECU.sln b/UnlockECU/UnlockECU.sln new file mode 100644 index 0000000..73772b5 --- /dev/null +++ b/UnlockECU/UnlockECU.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30711.63 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnlockECU", "UnlockECU\UnlockECU.csproj", "{9DBD6CB7-68B3-4801-95B7-D760C49E2A83}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisualUnlockECU", "VisualUnlockECU\VisualUnlockECU.csproj", "{FA2EF721-9B46-4EB9-8444-0ABAF6F5185A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnlockECUTests", "UnlockECUTests\UnlockECUTests.csproj", "{6252DB08-EFD2-4889-B97F-090726D9DC9C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9DBD6CB7-68B3-4801-95B7-D760C49E2A83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9DBD6CB7-68B3-4801-95B7-D760C49E2A83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9DBD6CB7-68B3-4801-95B7-D760C49E2A83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9DBD6CB7-68B3-4801-95B7-D760C49E2A83}.Release|Any CPU.Build.0 = Release|Any CPU + {FA2EF721-9B46-4EB9-8444-0ABAF6F5185A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA2EF721-9B46-4EB9-8444-0ABAF6F5185A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA2EF721-9B46-4EB9-8444-0ABAF6F5185A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA2EF721-9B46-4EB9-8444-0ABAF6F5185A}.Release|Any CPU.Build.0 = Release|Any CPU + {6252DB08-EFD2-4889-B97F-090726D9DC9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6252DB08-EFD2-4889-B97F-090726D9DC9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6252DB08-EFD2-4889-B97F-090726D9DC9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6252DB08-EFD2-4889-B97F-090726D9DC9C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CC1E2894-FC7F-474F-8A14-00529B7EF07F} + EndGlobalSection +EndGlobal diff --git a/UnlockECU/UnlockECU/BitUtility.cs b/UnlockECU/UnlockECU/BitUtility.cs new file mode 100644 index 0000000..b78fd6d --- /dev/null +++ b/UnlockECU/UnlockECU/BitUtility.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Utilities for bit and byte operations. + /// (Frequently copied-and-pasted across my projects) + /// + public class BitUtility + { + /// + /// Sets all values in an array of bytes to a specific value + /// + /// Value to set byte array to + /// Target byte array buffer + public static void Memset(byte value, byte[] buf) + { + for (int i = 0; i < buf.Length; i++) + { + buf[i] = value; + } + } + // Internally used by BytesFromHex + private static byte[] StringToByteArrayFastest(string hex) + { + // see https://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array + if (hex.Length % 2 == 1) + { + throw new Exception("The binary key cannot have an odd number of digits"); + } + byte[] arr = new byte[hex.Length >> 1]; + for (int i = 0; i < hex.Length >> 1; ++i) + { + arr[i] = (byte)((GetHexValue(hex[i << 1]) << 4) + (GetHexValue(hex[(i << 1) + 1]))); + } + return arr; + } + // Internally used by StringToByteArrayFastest + private static int GetHexValue(char hex) + { + int val = (int)hex; + return val - (val < 58 ? 48 : 55); + } + /// + /// Converts an array of bytes into its hex-string equivalent + /// + /// Input byte array + /// Option to add spaces between individual bytes + /// Hex-string based on the input byte array + public static string BytesToHex(byte[] inBytes, bool spacedOut = false) + { + return BitConverter.ToString(inBytes).Replace("-", spacedOut ? " " : ""); + } + + /// + /// Converts an array of bytes into a printable hex-string + /// + /// Input hex-string to convert into a byte array + /// Byte array based on the input hex-string + public static byte[] BytesFromHex(string hexString) + { + return StringToByteArrayFastest(hexString.Replace(" ", "")); + } + + /// + /// Resize a smaller array of bytes to a larger array. The padding bytes will be 0. + /// + /// Input byte array + /// New size for the input array + /// Resized byte array + public static byte[] PadBytes(byte[] inData, int finalSize) + { + if (inData.Length > finalSize) + { + return inData; + } + byte[] result = new byte[finalSize]; + Buffer.BlockCopy(inData, 0, result, 0, inData.Length); + return result; + } + + } +} diff --git a/UnlockECU/UnlockECU/Definition.cs b/UnlockECU/UnlockECU/Definition.cs new file mode 100644 index 0000000..76352dd --- /dev/null +++ b/UnlockECU/UnlockECU/Definition.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + public class Definition + { + public string EcuName { get; set; } + public int AccessLevel { get; set; } + public int SeedLength { get; set; } + public int KeyLength { get; set; } + public string Provider { get; set; } + public string Origin { get; set; } + public List Parameters { get; set; } + + [System.Text.Json.Serialization.JsonIgnore] + public string ParamParent; + + public override string ToString() + { + StringBuilder sb = new StringBuilder($"Name: {EcuName} ({Origin}), Level: {AccessLevel}, Seed Length: {SeedLength}, Key Length: {KeyLength}, Provider: {Provider}"); + return sb.ToString(); + /* + // uncomment and remove the return above to print verbose parameter data + foreach (Parameter row in Parameters) + { + sb.AppendLine(); + sb.Append($"Parameter[{row.Key}] ({row.DataType}) : {row.Value}"); + } + return sb.ToString(); + */ + } + + } +} diff --git a/UnlockECU/UnlockECU/Parameter.cs b/UnlockECU/UnlockECU/Parameter.cs new file mode 100644 index 0000000..598e114 --- /dev/null +++ b/UnlockECU/UnlockECU/Parameter.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + public class Parameter + { + public string Key { get; set; } + public string Value { get; set; } + public string DataType { get; set; } + [System.Text.Json.Serialization.JsonIgnore] + public int AccessLevel = -1; + } +} diff --git a/UnlockECU/UnlockECU/Program.cs b/UnlockECU/UnlockECU/Program.cs new file mode 100644 index 0000000..2b631e3 --- /dev/null +++ b/UnlockECU/UnlockECU/Program.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; + +namespace UnlockECU +{ + class Program + { + + static void Main(string[] args) + { + Console.WriteLine("UnlockECU (Running as console application)"); + + string definitionJson = File.ReadAllText("db.json"); + + List definitions = System.Text.Json.JsonSerializer.Deserialize>(definitionJson); + List providers = SecurityProvider.GetSecurityProviders(); + + ReverseKey(BitUtility.BytesFromHex("BA00D268 972D452D"), BitUtility.BytesFromHex("BE515D46")); // reverse key 0x3F9C71A5 (CRD3S2SEC9A) + + Console.ReadKey(); + } + + static void ReverseKey(byte[] inSeed, byte[] outKeyBytes) + { + long kA = 1103515245L; + long kC = 12345L; + + long seedA = BytesToInt(inSeed, Endian.Big, 0); + long seedB = BytesToInt(inSeed, Endian.Big, 4); + + long outKey = BytesToInt(outKeyBytes, Endian.Big); + + long intermediate1 = kA * seedA + kC; + long intermediate2 = kA * seedB + kC; + + long xorA = intermediate1 ^ intermediate2; + + long reverseCryptoKey = (xorA ^ outKey) & 0xFFFFFFFF; // reverse key 0x3F9C71A5 (CRD3S2SEC9A) + + Console.WriteLine($"Reversed DSSA key: {reverseCryptoKey:X}"); + } + + 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; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/DRVU_PROF.cs b/UnlockECU/UnlockECU/Security/DRVU_PROF.cs new file mode 100644 index 0000000..d5decde --- /dev/null +++ b/UnlockECU/UnlockECU/Security/DRVU_PROF.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Simpler version of DaimlerStandardSecurityAlgo with custom kA, kC, and no blockB + /// + class DRVU_PROF : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[] cryptoKeyBytes = GetParameterBytearray(parameters, "KeyConst"); + uint cryptoKey = BytesToInt(cryptoKeyBytes, Endian.Big); + + long kA = 258028488L; + long kC = 1583629211L; + + if ((inSeed.Length != 4) || (outKey.Length != 4)) + { + return false; + } + + long seedA = BytesToInt(inSeed, Endian.Big, 0); + + long intermediate1 = kA * seedA + kC; + long seedKey = intermediate1 % cryptoKey; + + IntToBytes((uint)seedKey, outKey, Endian.Big); + return true; + } + + public override string GetProviderName() + { + return "DRVU_PROF"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/DaimlerStandardSecurityAlgo.cs b/UnlockECU/UnlockECU/Security/DaimlerStandardSecurityAlgo.cs new file mode 100644 index 0000000..11dc018 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/DaimlerStandardSecurityAlgo.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Basic implementation DaimlerStandardSecurityAlgo, with hardcoded kA, kC constants for the intermediate transformation. + /// + class DaimlerStandardSecurityAlgo : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[] cryptoKeyBytes = GetParameterBytearray(parameters, "K"); + uint cryptoKey = BytesToInt(cryptoKeyBytes, Endian.Big); + + long kA = 1103515245L; + long kC = 12345L; + + if ((inSeed.Length != 8) || (outKey.Length != 4)) + { + return false; + } + + long seedA = BytesToInt(inSeed, Endian.Big, 0); + long seedB = BytesToInt(inSeed, Endian.Big, 4); + + long intermediate1 = kA * seedA + kC; + long intermediate2 = kA * seedB + kC; + long seedKey = intermediate1 ^ intermediate2 ^ cryptoKey; + + IntToBytes((uint)seedKey, outKey, Endian.Big); + return true; + } + public override string GetProviderName() + { + return "DaimlerStandardSecurityAlgo"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/DaimlerStandardSecurityAlgoMod.cs b/UnlockECU/UnlockECU/Security/DaimlerStandardSecurityAlgoMod.cs new file mode 100644 index 0000000..837c1eb --- /dev/null +++ b/UnlockECU/UnlockECU/Security/DaimlerStandardSecurityAlgoMod.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Derivative of DaimlerStandardSecurityAlgo, with defined kA, kC constants for the intermediate transformation. + /// + class DaimlerStandardSecurityAlgoMod : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[] cryptoKeyBytes = GetParameterBytearray(parameters, "K"); + uint cryptoKey = BytesToInt(cryptoKeyBytes, Endian.Big); + + long kA = GetParameterLong(parameters, "kA"); + long kC = GetParameterLong(parameters, "kC"); + + if ((inSeed.Length != 8) || (outKey.Length != 4)) + { + return false; + } + + long seedA = BytesToInt(inSeed, Endian.Big, 0); + long seedB = BytesToInt(inSeed, Endian.Big, 4); + + long intermediate1 = kA * seedA + kC; + long intermediate2 = kA * seedB + kC; + + long seedKey = intermediate1 ^ intermediate2 ^ cryptoKey; + + IntToBytes((uint)seedKey, outKey, Endian.Big); + return true; + } + + public override string GetProviderName() + { + return "DaimlerStandardSecurityAlgoMod"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/DaimlerStandardSecurityAlgoRefG.cs b/UnlockECU/UnlockECU/Security/DaimlerStandardSecurityAlgoRefG.cs new file mode 100644 index 0000000..fdd7a50 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/DaimlerStandardSecurityAlgoRefG.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Derivative of DaimlerStandardSecurityAlgoMod, with 4 custom values (kA0, kA1, kC0, kC1) for the intermediate transformation. + /// + class DaimlerStandardSecurityAlgoRefG : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[] cryptoKeyBytes = GetParameterBytearray(parameters, "K_refG"); + uint cryptoKey = BytesToInt(cryptoKeyBytes, Endian.Big); + + long kA_0 = 3040238857L; + long kA_1 = 4126034881L; + long kC_0 = 2094854071L; + long kC_1 = 3555108353L; + + if ((inSeed.Length != 8) || (outKey.Length != 4)) + { + return false; + } + + long seedA = BytesToInt(inSeed, Endian.Big, 0); + long seedB = BytesToInt(inSeed, Endian.Big, 4); + + long intermediate1 = kA_0 * seedA + kC_0; + long intermediate2 = kA_1 * seedB + kC_1; + long seedKey = intermediate1 ^ intermediate2 ^ cryptoKey; + + IntToBytes((uint)seedKey, outKey, Endian.Big); + return true; + } + + public override string GetProviderName() + { + return "DaimlerStandardSecurityAlgoRefG"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/EDIFF290.cs b/UnlockECU/UnlockECU/Security/EDIFF290.cs new file mode 100644 index 0000000..ac774de --- /dev/null +++ b/UnlockECU/UnlockECU/Security/EDIFF290.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Simpler version of DaimlerStandardSecurityAlgo with custom kA, kC, and no blockB. + /// Similar to DRVU_PROF, with different initial parameter data types + /// + class EDIFF290 : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[] cryptoKeyBytes = GetParameterBytearray(parameters, "KeyK"); + uint cryptoKey = BytesToInt(cryptoKeyBytes, Endian.Big); + + long kA = GetParameterInteger(parameters, "kA"); + long kC = GetParameterInteger(parameters, "kC"); + + if ((inSeed.Length != 8) || (outKey.Length != 4)) + { + return false; + } + + long seedA = BytesToInt(inSeed, Endian.Big, 0); + + // kA * seedA + kC, but constrained to 32bits + long intermediate1 = kA * seedA; + intermediate1 &= 0xFFFFFFFF; + intermediate1 += kC; + intermediate1 &= 0xFFFFFFFF; + + long seedKey = intermediate1 % cryptoKey; + + IntToBytes((uint)seedKey, outKey, Endian.Big); + return true; + } + + public override string GetProviderName() + { + return "EDIFF290"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/ESPSecurityAlgoLevel1.cs b/UnlockECU/UnlockECU/Security/ESPSecurityAlgoLevel1.cs new file mode 100644 index 0000000..8654b5e --- /dev/null +++ b/UnlockECU/UnlockECU/Security/ESPSecurityAlgoLevel1.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + class ESPSecurityAlgoLevel1 : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + if ((inSeed.Length != 2) || (outKey.Length != 2)) + { + return false; + } + + uint seedAsInt = inSeed[1] | ((uint)inSeed[0] << 8); + uint key = 4 * ((seedAsInt >> 3) ^ seedAsInt) ^ seedAsInt; + + outKey[0] = (byte)(key >> 8); + outKey[1] = (byte)(key >> 0); + return true; + } + + public override string GetProviderName() + { + return "ESPSecurityAlgoLevel1"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/EsLibEd25519.cs b/UnlockECU/UnlockECU/Security/EsLibEd25519.cs new file mode 100644 index 0000000..6d6ea43 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/EsLibEd25519.cs @@ -0,0 +1,38 @@ +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Used for modern ECUs. Ed25519PH with an empty context. + /// + class EsLibEd25519 : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[] privateKeyBytes = GetParameterBytearray(parameters, "PrivateKey"); + + if ((inSeed.Length != 32) || (outKey.Length != 64)) + { + return false; + } + + Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(privateKeyBytes, 0); + Ed25519phSigner signer = new Ed25519phSigner(new byte[] { }); + signer.Init(true, privateKey); + signer.BlockUpdate(inSeed, 0, inSeed.Length); + byte[] signature = signer.GenerateSignature(); + Array.ConstrainedCopy(signature, 0, outKey, 0, signature.Length); + return true; + } + public override string GetProviderName() + { + return "EsLibEd25519"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/MarquardtSecurityAlgo.cs b/UnlockECU/UnlockECU/Security/MarquardtSecurityAlgo.cs new file mode 100644 index 0000000..765c30a --- /dev/null +++ b/UnlockECU/UnlockECU/Security/MarquardtSecurityAlgo.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + class MarquardtSecurityAlgo : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[] constMBytes = GetParameterBytearray(parameters, "const_M"); + byte[] constCBytes = GetParameterBytearray(parameters, "const_C"); + byte[] constABytes = GetParameterBytearray(parameters, "const_A"); + + uint constM = BytesToInt(constMBytes, Endian.Big); // 228 + uint constC = BytesToInt(constCBytes, Endian.Big); // 236 + uint constA = BytesToInt(constABytes, Endian.Big); // 232 + uint inSeedAsInt = BytesToInt(inSeed, Endian.Big); // 232 + + if ((inSeed.Length != 4) || (outKey.Length != 4)) + { + return false; + } + + uint outKeyInt = 0; + unchecked + { + outKeyInt = constC + inSeedAsInt * constA % constM; + } + + IntToBytes(outKeyInt, outKey, Endian.Big); + + return true; + } + public override string GetProviderName() + { + return "MarquardtSecurityAlgo"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/OCM172.cs b/UnlockECU/UnlockECU/Security/OCM172.cs new file mode 100644 index 0000000..34cc31a --- /dev/null +++ b/UnlockECU/UnlockECU/Security/OCM172.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Passes the input seed as the output key (2 bytes) + /// + class OCM172 : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + if ((inSeed.Length != 2) || (outKey.Length != 2)) + { + return false; + } + + outKey[0] = inSeed[0]; + outKey[1] = inSeed[1]; + return true; + } + + public override string GetProviderName() + { + return "OCM172"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/PowertrainBoschContiSecurityAlgo1.cs b/UnlockECU/UnlockECU/Security/PowertrainBoschContiSecurityAlgo1.cs new file mode 100644 index 0000000..740be20 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/PowertrainBoschContiSecurityAlgo1.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Appears to be specific to MED97 + /// + class PowertrainBoschContiSecurityAlgo1 : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[] ubTable = GetParameterBytearray(parameters, "ubTable"); + byte[] Mask = GetParameterBytearray(parameters, "Mask"); + + if ((inSeed.Length != 2) || (outKey.Length != 2)) + { + return false; + } + + uint inSeedAsInt = inSeed[1] | ((uint)inSeed[0] << 8); + uint MaskAsInt = Mask[1] | ((uint)Mask[0] << 8); + + uint swBit1 = (inSeedAsInt & MaskAsInt & 0x4000) >> 12; + uint swBit2 = (inSeedAsInt & MaskAsInt & 0x200) >> 8; + uint swBit3 = (inSeedAsInt & MaskAsInt & 0x100) >> 8; + + uint keyAsInt = ubTable[swBit1 | swBit2 | swBit3] * inSeedAsInt; + + outKey[0] = (byte)((keyAsInt >> 16) & 0xFF); + outKey[1] = (byte)((keyAsInt >> 8) & 0xFF); + + return true; + } + public override string GetProviderName() + { + return "PowertrainBoschContiSecurityAlgo1"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/PowertrainBoschContiSecurityAlgo2.cs b/UnlockECU/UnlockECU/Security/PowertrainBoschContiSecurityAlgo2.cs new file mode 100644 index 0000000..0b259b8 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/PowertrainBoschContiSecurityAlgo2.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Appears to be specific to MED97, SIM271CNG906 + /// + class PowertrainBoschContiSecurityAlgo2 : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[] Table = GetParameterBytearray(parameters, "Table"); + byte[] uwMasc = GetParameterBytearray(parameters, "uwMasc"); + + if ((inSeed.Length != 2) || (outKey.Length != 2)) + { + return false; + } + + uint shiftIndexA = 1; + uint shiftIndexB = 1; + uint activatedBits = 0; + + uint inSeedAsInt = inSeed[1] | ((uint)inSeed[0] << 8); + uint uwMascAsInt = uwMasc[1] | ((uint)uwMasc[0] << 8); + + for (int i = 0; i < 16; i++) + { + if ((shiftIndexA & uwMascAsInt) > 0) + { + if ((shiftIndexA & inSeedAsInt) > 0) + { + activatedBits |= shiftIndexB; + } + shiftIndexB *= 2; + } + shiftIndexA *= 2; + } + + uint keyAsInt = (Table[activatedBits] * inSeedAsInt) >> 8; + keyAsInt &= 0xFFFF; + + outKey[0] = (byte)((keyAsInt >> 8) & 0xFF); + outKey[1] = (byte)((keyAsInt >> 0) & 0xFF); + + return true; + } + public override string GetProviderName() + { + return "PowertrainBoschContiSecurityAlgo2"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/PowertrainDelphiSecurityAlgo.cs b/UnlockECU/UnlockECU/Security/PowertrainDelphiSecurityAlgo.cs new file mode 100644 index 0000000..612e104 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/PowertrainDelphiSecurityAlgo.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Similar to generic PowertrainSecurityAlgo with a fixed i and j table, specific to CRD2, CRD3, CRD3S2 family + /// + class PowertrainDelphiSecurityAlgo : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[][] dValueMatrix = new byte[][] { + GetParameterBytearray(parameters, "D_VALUE_0"), + GetParameterBytearray(parameters, "D_VALUE_1"), + GetParameterBytearray(parameters, "D_VALUE_2"), + GetParameterBytearray(parameters, "D_VALUE_3"), + GetParameterBytearray(parameters, "D_VALUE_4"), + GetParameterBytearray(parameters, "D_VALUE_5"), + GetParameterBytearray(parameters, "D_VALUE_6"), + GetParameterBytearray(parameters, "D_VALUE_7"), + }; + byte[][] gValueMatrix = new byte[][] { + GetParameterBytearray(parameters, "G_VALUE_0"), + GetParameterBytearray(parameters, "G_VALUE_1"), + GetParameterBytearray(parameters, "G_VALUE_2"), + GetParameterBytearray(parameters, "G_VALUE_3"), + GetParameterBytearray(parameters, "G_VALUE_4"), + GetParameterBytearray(parameters, "G_VALUE_5"), + GetParameterBytearray(parameters, "G_VALUE_6"), + GetParameterBytearray(parameters, "G_VALUE_7"), + }; + + if ((inSeed.Length != 4) || (outKey.Length != 4)) + { + return false; + } + + byte[] workingSeed = new byte[] { inSeed[3], inSeed[2], inSeed[1], inSeed[0] }; + + byte y = (byte)(workingSeed[1] ^ workingSeed[0]); + int dBit2 = GetBit((byte)(workingSeed[1] ^ workingSeed[0]), 3); + int dBit1 = GetBit(workingSeed[2], 3); + int dBit0 = GetBit(workingSeed[3], 6); + uint dValue = CreateDValue(dBit2, dBit1, dBit0, dValueMatrix); + + uint seedAsInt = BytesToInt(workingSeed, Endian.Little); + + uint dXorIntermediate = seedAsInt ^ dValue; + + int gBit0 = GetBit(workingSeed[1], 3); + int gBit1 = GetBit(y, 7); + int gBit2 = GetBit(GetByte(dXorIntermediate, 1), 2); + uint gValue = CreateDValue(gBit2, gBit1, gBit0, gValueMatrix); + + uint seedKey = dXorIntermediate ^ gValue; + IntToBytes(seedKey, outKey, Endian.Big); + + return true; + } + + private uint CreateDValue(int bit2Enabled, int bit1Enabled, int bit0Enabled, byte[][] matrix) + { + uint i = 0; + byte j = 0; + if (bit0Enabled != 0) + { + j = SetBit(j, 0); + } + if (bit1Enabled != 0) + { + j = SetBit(j, 1); + } + if (bit2Enabled != 0) + { + j = SetBit(j, 2); + } + i = SetByte(i, matrix[j][3], 0); + i = SetByte(i, matrix[j][2], 1); + i = SetByte(i, matrix[j][1], 2); + i = SetByte(i, matrix[j][0], 3); + return i; + } + + public override string GetProviderName() + { + return "PowertrainDelphiSecurityAlgo"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/PowertrainSecurityAlgo.cs b/UnlockECU/UnlockECU/Security/PowertrainSecurityAlgo.cs new file mode 100644 index 0000000..8865782 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/PowertrainSecurityAlgo.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Basic implementation PowertrainSecurityAlgo. + /// + class PowertrainSecurityAlgo : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[][] matrix = new byte[][] + { + new byte[]{ GetParameterByte(parameters, "X00"), GetParameterByte(parameters, "X01"), GetParameterByte(parameters, "X02"), GetParameterByte(parameters, "X03") }, + new byte[]{ GetParameterByte(parameters, "X10"), GetParameterByte(parameters, "X11"), GetParameterByte(parameters, "X12"), GetParameterByte(parameters, "X13") }, + new byte[]{ GetParameterByte(parameters, "X20"), GetParameterByte(parameters, "X21"), GetParameterByte(parameters, "X22"), GetParameterByte(parameters, "X23") }, + new byte[]{ GetParameterByte(parameters, "X30"), GetParameterByte(parameters, "X31"), GetParameterByte(parameters, "X32"), GetParameterByte(parameters, "X33") }, + new byte[]{ GetParameterByte(parameters, "X40"), GetParameterByte(parameters, "X41"), GetParameterByte(parameters, "X42"), GetParameterByte(parameters, "X43") }, + new byte[]{ GetParameterByte(parameters, "X50"), GetParameterByte(parameters, "X51"), GetParameterByte(parameters, "X52"), GetParameterByte(parameters, "X53") }, + new byte[]{ GetParameterByte(parameters, "X60"), GetParameterByte(parameters, "X61"), GetParameterByte(parameters, "X62"), GetParameterByte(parameters, "X63") }, + new byte[]{ GetParameterByte(parameters, "X70"), GetParameterByte(parameters, "X71"), GetParameterByte(parameters, "X72"), GetParameterByte(parameters, "X73") }, + }; + + int[] i = new int[] + { + GetParameterInteger(parameters, "i1"), + GetParameterInteger(parameters, "i2"), + GetParameterInteger(parameters, "i3"), + GetParameterInteger(parameters, "i4"), + GetParameterInteger(parameters, "i5"), + GetParameterInteger(parameters, "i6") + }; + int[] j = new int[] + { + GetParameterInteger(parameters, "j1"), + GetParameterInteger(parameters, "j2"), + GetParameterInteger(parameters, "j3"), + GetParameterInteger(parameters, "j4"), + GetParameterInteger(parameters, "j5"), + GetParameterInteger(parameters, "j6") + }; + + if ((inSeed.Length != 4) || (outKey.Length != 4)) + { + return false; + } + + byte[] workingSeed = new byte[] { inSeed[3], inSeed[2], inSeed[1], inSeed[0] }; + + byte y = (byte)(workingSeed[i[0]] ^ workingSeed[i[1]]); + int dBit2 = GetBit(workingSeed[i[2]], j[0]); + int dBit1 = GetBit(workingSeed[i[3]], j[1]); + int dBit0 = GetBit(y, j[2]); + uint dValue = CreateDValue(dBit2, dBit1, dBit0, matrix); + + uint seedAsInt = BytesToInt(workingSeed, Endian.Little); + + uint dXorIntermediate = seedAsInt ^ dValue; + int gBit2 = GetBit(workingSeed[i[4]], j[3]); + int gBit1 = GetBit(y, j[4]); + int gBit0 = GetBit(GetByte(dXorIntermediate, i[5]), j[5]); + uint gValue = CreateGValue(gBit2, gBit1, gBit0, matrix); + + uint seedKey = dXorIntermediate ^ gValue; + + IntToBytes(seedKey, outKey, Endian.Big); + return true; + } + + private uint CreateDValue(int bit2Enabled, int bit1Enabled, int bit0Enabled, byte[][] matrix) + { + uint i = 0; + byte j = 0; + if (bit0Enabled != 0) + { + j = SetBit(j, 0); + } + if (bit1Enabled != 0) + { + j = SetBit(j, 1); + } + if (bit2Enabled != 0) + { + j = SetBit(j, 2); + } + i = SetByte(i, matrix[j][3], 0); + i = SetByte(i, matrix[j][2], 1); + i = SetByte(i, matrix[j][1], 2); + i = SetByte(i, matrix[j][0], 3); + return i; + } + + private uint CreateGValue(int bit2Enabled, int bit1Enabled, int bit0Enabled, byte[][] matrix) + { + uint i = 0; + byte j = 0; + if (bit0Enabled != 0) + { + j = SetBit(j, 0); + } + if (bit1Enabled != 0) + { + j = SetBit(j, 1); + } + if (bit2Enabled != 0) + { + j = SetBit(j, 2); + } + i = SetByte(i, matrix[j][2], 0); + i = SetByte(i, matrix[j][1], 1); + i = SetByte(i, matrix[j][0], 2); + i = SetByte(i, matrix[j][3], 3); + return i; + } + + public override string GetProviderName() + { + return "PowertrainSecurityAlgo"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/PowertrainSecurityAlgo2.cs b/UnlockECU/UnlockECU/Security/PowertrainSecurityAlgo2.cs new file mode 100644 index 0000000..76d2f7a --- /dev/null +++ b/UnlockECU/UnlockECU/Security/PowertrainSecurityAlgo2.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Derivative of PowertrainSecurityAlgo, with a slightly different byte ordering when computing the g-value. + /// + class PowertrainSecurityAlgo2 : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[][] matrix = new byte[][] + { + new byte[]{ GetParameterByte(parameters, "XX00"), GetParameterByte(parameters, "XX01"), GetParameterByte(parameters, "XX02"), GetParameterByte(parameters, "XX03") }, + new byte[]{ GetParameterByte(parameters, "XX10"), GetParameterByte(parameters, "XX11"), GetParameterByte(parameters, "XX12"), GetParameterByte(parameters, "XX13") }, + new byte[]{ GetParameterByte(parameters, "XX20"), GetParameterByte(parameters, "XX21"), GetParameterByte(parameters, "XX22"), GetParameterByte(parameters, "XX23") }, + new byte[]{ GetParameterByte(parameters, "XX30"), GetParameterByte(parameters, "XX31"), GetParameterByte(parameters, "XX32"), GetParameterByte(parameters, "XX33") }, + new byte[]{ GetParameterByte(parameters, "XX40"), GetParameterByte(parameters, "XX41"), GetParameterByte(parameters, "XX42"), GetParameterByte(parameters, "XX43") }, + new byte[]{ GetParameterByte(parameters, "XX50"), GetParameterByte(parameters, "XX51"), GetParameterByte(parameters, "XX52"), GetParameterByte(parameters, "XX53") }, + new byte[]{ GetParameterByte(parameters, "XX60"), GetParameterByte(parameters, "XX61"), GetParameterByte(parameters, "XX62"), GetParameterByte(parameters, "XX63") }, + new byte[]{ GetParameterByte(parameters, "XX70"), GetParameterByte(parameters, "XX71"), GetParameterByte(parameters, "XX72"), GetParameterByte(parameters, "XX73") }, + }; + + int[] i = new int[] + { + GetParameterInteger(parameters, "ii1"), + GetParameterInteger(parameters, "ii2"), + GetParameterInteger(parameters, "ii3"), + GetParameterInteger(parameters, "ii4"), + GetParameterInteger(parameters, "ii5"), + GetParameterInteger(parameters, "ii6") + }; + int[] j = new int[] + { + GetParameterInteger(parameters, "jj1"), + GetParameterInteger(parameters, "jj2"), + GetParameterInteger(parameters, "jj3"), + GetParameterInteger(parameters, "jj4"), + GetParameterInteger(parameters, "jj5"), + GetParameterInteger(parameters, "jj6") + }; + + if ((inSeed.Length != 4) || (outKey.Length != 4)) + { + return false; + } + + byte[] workingSeed = new byte[] { inSeed[3], inSeed[2], inSeed[1], inSeed[0] }; + + byte y = (byte)(workingSeed[i[0]] ^ workingSeed[i[1]]); + int dBit2 = GetBit(workingSeed[i[2]], j[0]); + int dBit1 = GetBit(workingSeed[i[3]], j[1]); + int dBit0 = GetBit(y, j[2]); + uint dValue = CreateDValue(dBit2, dBit1, dBit0, matrix); + + uint seedAsInt = BytesToInt(workingSeed, Endian.Little); + + uint dXorIntermediate = seedAsInt ^ dValue; + int gBit2 = GetBit(workingSeed[i[4]], j[3]); + int gBit1 = GetBit(y, j[4]); + int gBit0 = GetBit(GetByte(dXorIntermediate, i[5]), j[5]); + uint gValue = CreateGValue(gBit2, gBit1, gBit0, matrix); + + uint seedKey = dXorIntermediate ^ gValue; + + IntToBytes(seedKey, outKey, Endian.Big); + return true; + } + + private uint CreateDValue(int bit2Enabled, int bit1Enabled, int bit0Enabled, byte[][] matrix) + { + uint i = 0; + byte j = 0; + if (bit0Enabled != 0) + { + j = SetBit(j, 0); + } + if (bit1Enabled != 0) + { + j = SetBit(j, 1); + } + if (bit2Enabled != 0) + { + j = SetBit(j, 2); + } + i = SetByte(i, matrix[j][3], 0); + i = SetByte(i, matrix[j][2], 1); + i = SetByte(i, matrix[j][1], 2); + i = SetByte(i, matrix[j][0], 3); + return i; + } + + private uint CreateGValue(int bit2Enabled, int bit1Enabled, int bit0Enabled, byte[][] matrix) + { + uint i = 0; + byte j = 0; + if (bit0Enabled != 0) + { + j = SetBit(j, 0); + } + if (bit1Enabled != 0) + { + j = SetBit(j, 1); + } + if (bit2Enabled != 0) + { + j = SetBit(j, 2); + } + i = SetByte(i, matrix[j][0], 0); + i = SetByte(i, matrix[j][3], 1); + i = SetByte(i, matrix[j][2], 2); + i = SetByte(i, matrix[j][1], 3); + return i; + } + + public override string GetProviderName() + { + return "PowertrainSecurityAlgo2"; + } + } +} + diff --git a/UnlockECU/UnlockECU/Security/PowertrainSecurityAlgoNFZ.cs b/UnlockECU/UnlockECU/Security/PowertrainSecurityAlgoNFZ.cs new file mode 100644 index 0000000..4c1b155 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/PowertrainSecurityAlgoNFZ.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Similar to PowertrainDelphiSecurityAlgo + /// + class PowertrainSecurityAlgoNFZ : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[][] dValueMatrix = new byte[][] { + GetParameterBytearray(parameters, "D_VALUE_0"), + GetParameterBytearray(parameters, "D_VALUE_1"), + GetParameterBytearray(parameters, "D_VALUE_2"), + GetParameterBytearray(parameters, "D_VALUE_3"), + GetParameterBytearray(parameters, "D_VALUE_4"), + GetParameterBytearray(parameters, "D_VALUE_5"), + GetParameterBytearray(parameters, "D_VALUE_6"), + GetParameterBytearray(parameters, "D_VALUE_7"), + }; + byte[][] gValueMatrix = new byte[][] { + GetParameterBytearray(parameters, "G_VALUE_0"), + GetParameterBytearray(parameters, "G_VALUE_1"), + GetParameterBytearray(parameters, "G_VALUE_2"), + GetParameterBytearray(parameters, "G_VALUE_3"), + GetParameterBytearray(parameters, "G_VALUE_4"), + GetParameterBytearray(parameters, "G_VALUE_5"), + GetParameterBytearray(parameters, "G_VALUE_6"), + GetParameterBytearray(parameters, "G_VALUE_7"), + }; + + if ((inSeed.Length != 4) || (outKey.Length != 4)) + { + return false; + } + + byte[] workingSeed = new byte[] { inSeed[3], inSeed[2], inSeed[1], inSeed[0] }; + + int dBit2 = GetBit((byte)(workingSeed[3] ^ workingSeed[1]), 1); + int dBit1 = GetBit(workingSeed[2], 4); + int dBit0 = GetBit(workingSeed[0], 6); + uint dValue = CreateDValue(dBit2, dBit1, dBit0, dValueMatrix); + + uint seedAsInt = BytesToInt(workingSeed, Endian.Little); + + uint dXorIntermediate = seedAsInt ^ dValue; + + int gBit0 = GetBit(GetByte(dXorIntermediate, 3), 4); + int gBit1 = GetBit(workingSeed[0], 1); + int gBit2 = GetBit(workingSeed[2], 6); + + uint gValue = CreateDValue(gBit2, gBit1, gBit0, gValueMatrix); + + uint seedKey = dXorIntermediate ^ gValue; + IntToBytes(seedKey, outKey, Endian.Big); + + return true; + } + + private uint CreateDValue(int bit2Enabled, int bit1Enabled, int bit0Enabled, byte[][] matrix) + { + uint i = 0; + byte j = 0; + if (bit0Enabled != 0) + { + j = SetBit(j, 0); + } + if (bit1Enabled != 0) + { + j = SetBit(j, 1); + } + if (bit2Enabled != 0) + { + j = SetBit(j, 2); + } + i = SetByte(i, matrix[j][3], 0); + i = SetByte(i, matrix[j][2], 1); + i = SetByte(i, matrix[j][1], 2); + i = SetByte(i, matrix[j][0], 3); + return i; + } + + public override string GetProviderName() + { + return "PowertrainSecurityAlgoNFZ"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/RBTM.cs b/UnlockECU/UnlockECU/Security/RBTM.cs new file mode 100644 index 0000000..b9084b4 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/RBTM.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Passes the input seed as the output key (4 bytes) + /// + class RBTM : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + if ((inSeed.Length != 4) || (outKey.Length != 4)) + { + return false; + } + + outKey[0] = inSeed[0]; + outKey[1] = inSeed[1]; + outKey[2] = inSeed[2]; + outKey[3] = inSeed[3]; + return true; + } + + public override string GetProviderName() + { + return "RBTM"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/RDU222.cs b/UnlockECU/UnlockECU/Security/RDU222.cs new file mode 100644 index 0000000..49a9478 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/RDU222.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +#pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand +namespace UnlockECU +{ + /// + /// RDU222: seed |= A, ^= B, += C + /// + class RDU222 : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + long paramA = BytesToInt(GetParameterBytearray(parameters, "a"), Endian.Big); + long paramB = BytesToInt(GetParameterBytearray(parameters, "b"), Endian.Big); + long paramC = BytesToInt(GetParameterBytearray(parameters, "c"), Endian.Big); + + if ((inSeed.Length != 4) || (outKey.Length != 4)) + { + return false; + } + + long inSeedAsLong = BytesToInt(inSeed, Endian.Big); + + inSeedAsLong |= paramA; + inSeedAsLong ^= paramB; + inSeedAsLong += paramC; + + IntToBytes((uint)inSeedAsLong, outKey, Endian.Big); + return true; + } + + public override string GetProviderName() + { + return "RDU222"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/RVC222_MPC222_FCW246_LRR3.cs b/UnlockECU/UnlockECU/Security/RVC222_MPC222_FCW246_LRR3.cs new file mode 100644 index 0000000..d30d6ce --- /dev/null +++ b/UnlockECU/UnlockECU/Security/RVC222_MPC222_FCW246_LRR3.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +#pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand + +namespace UnlockECU +{ + /// + /// Similar to RDU222, with an extra XOR + /// + class RVC222_MPC222_FCW246_LRR3 : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + long paramA = BytesToInt(GetParameterBytearray(parameters, "A"), Endian.Big); + long paramB = BytesToInt(GetParameterBytearray(parameters, "B"), Endian.Big); + long paramC = BytesToInt(GetParameterBytearray(parameters, "C"), Endian.Big); + + if ((inSeed.Length != 4) || (outKey.Length != 4)) + { + return false; + } + + long inSeedAsLong = BytesToInt(inSeed, Endian.Big); + + inSeedAsLong |= paramA; + inSeedAsLong ^= paramB; + inSeedAsLong += paramC; + inSeedAsLong ^= 0xFFFFFFFF; + + IntToBytes((uint)inSeedAsLong, outKey, Endian.Big); + return true; + } + + public override string GetProviderName() + { + return "RVC222_MPC222_FCW246_LRR3"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/SWSP177.cs b/UnlockECU/UnlockECU/Security/SWSP177.cs new file mode 100644 index 0000000..696033c --- /dev/null +++ b/UnlockECU/UnlockECU/Security/SWSP177.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// SWSP177: behavior, param sizes don't match DLL + /// + class SWSP177 : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[] cryptoKeyBytes = GetParameterBytearray(parameters, "KeyConst"); + uint cryptoKey = BytesToInt(cryptoKeyBytes, Endian.Big); + + if ((inSeed.Length != 4) || (outKey.Length != 4)) + { + return false; + } + + ulong seedA = BytesToInt(inSeed, Endian.Big, 0); + + for (int i = 0; i < 35; i++) + { + seedA = seedA >> 1 | (seedA & 1) * 0x80000000; + seedA ^= cryptoKey; + } + + ulong seedKey = seedA & 0xFFFFFFFF; + + IntToBytes((uint)seedKey, outKey, Endian.Big); + return true; + } + + public override string GetProviderName() + { + return "SWSP177"; + } + } +} diff --git a/UnlockECU/UnlockECU/Security/SecurityProvider.cs b/UnlockECU/UnlockECU/Security/SecurityProvider.cs new file mode 100644 index 0000000..db7b707 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/SecurityProvider.cs @@ -0,0 +1,174 @@ +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 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 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 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 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 List(); + + 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 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 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 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 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 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 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; + } + } +} diff --git a/UnlockECU/UnlockECU/UnlockECU.csproj b/UnlockECU/UnlockECU/UnlockECU.csproj new file mode 100644 index 0000000..788548d --- /dev/null +++ b/UnlockECU/UnlockECU/UnlockECU.csproj @@ -0,0 +1,16 @@ + + + + Library + net5.0 + + + + AnyCPU + + + + + + + diff --git a/UnlockECU/UnlockECUTests/External/DllContext.cs b/UnlockECU/UnlockECUTests/External/DllContext.cs new file mode 100644 index 0000000..e715967 --- /dev/null +++ b/UnlockECU/UnlockECUTests/External/DllContext.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Security.Cryptography; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.IO; + +namespace UnlockECUTests +{ + /// + /// High level interface to access a Vector DLL + /// + public class DllContext + { + private IntPtr dllHandle = IntPtr.Zero; + public List DllExports = new List(); + private Dictionary dllAddressMappings = new Dictionary(); + + public string SHA1Hash = ""; + public string FileDescription = ""; + public string FileName = ""; + public string DLLPath = ""; + public string ECUName = ""; + public string Comment = ""; + public bool KeyGenerationCapability = false; + public bool ModeSpecified = false; + public List> AccessLevels = new List>(); + + public DllContext(string filePath, bool runHash = true) + { + DLLPath = filePath; + if (!File.Exists(DLLPath)) + { + Console.WriteLine($"{DLLPath}: File does not exist"); + return; + } + FileName = Path.GetFileName(filePath); + + // Compute and store the file hash + if (runHash) + { + using (var cryptoProvider = new SHA1CryptoServiceProvider()) + { + SHA1Hash = BitConverter.ToString(cryptoProvider.ComputeHash(File.ReadAllBytes(filePath))).Replace("-", ""); + } + } + + // Get the module's exports + DllExports = UnmanagedUtility.GetExports(DLLPath); + + if (DllExports.Count == 0) + { + Console.WriteLine($"{DLLPath}: No exports, possibly an invalid DLL"); + return; + } + + // Try to load the library into our process space + dllHandle = UnmanagedUtility.LoadLibrary(filePath); + if (dllHandle == IntPtr.Zero) + { + Console.WriteLine($"{DLLPath}: LoadLibrary failed"); + return; + } + + // Try to load addresses of all known exports + dllAddressMappings = new Dictionary(); + foreach (string knownExport in ExportDefinition.KnownExportedFunctions) + { + if (DllExports.Contains(knownExport)) + { + dllAddressMappings.Add(knownExport, UnmanagedUtility.GetProcAddress(dllHandle, knownExport)); + } + else + { + dllAddressMappings.Add(knownExport, IntPtr.Zero); + } + } + + // Set capabilities + KeyGenerationCapability = DllExports.Contains("GenerateKeyEx") || DllExports.Contains("GenerateKeyExOpt"); + ModeSpecified = DllExports.Contains("GetKeyLength") && DllExports.Contains("GetSeedLength") && DllExports.Contains("GetConfiguredAccessTypes"); + + // Store additional metadata + FileDescription = FileVersionInfo.GetVersionInfo(DLLPath).FileDescription; + + LoadAdditionalDataFromDllCalls(); + } + + public void LoadAdditionalDataFromDllCalls() + { + ECUName = GetECUName(); + Comment = GetComment(); + + if (!ModeSpecified) + { + return; + } + + // Access level, key size, seed size + AccessLevels = new List>(); + foreach (uint accessLevel in GetConfiguredAccessTypes()) + { + AccessLevels.Add(new Tuple(accessLevel, GetKeyLength(accessLevel), GetSeedLength(accessLevel))); + } + } + + // Automatically selects and invokes the correct key generation function. Prefers the "opt" variant + public byte[] GenerateKeyAuto(uint securityLevel, byte[] seed) + { + if (DllExports.Contains("GenerateKeyExOpt")) + { + return GenerateKey(seed, securityLevel, true, out ExportDefinition.VKeyGenResultEx result); + } + else if (DllExports.Contains("GenerateKeyEx")) + { + return GenerateKey(seed, securityLevel, false, out ExportDefinition.VKeyGenResultEx result); + } + else + { + return new byte[] { }; + } + } + + public string GetECUName() + { + IntPtr procAddress = dllAddressMappings["GetECUName"]; + if (procAddress == IntPtr.Zero) + { + return "(unavailable)"; + } + var fn = (ExportDefinition.GetECUName)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(ExportDefinition.GetECUName)); + IntPtr resultPtr = fn(); + return Marshal.PtrToStringAnsi(resultPtr); + } + public string GetComment() + { + IntPtr procAddress = dllAddressMappings["GetComment"]; + if (procAddress == IntPtr.Zero) + { + return "(unavailable)"; + } + var fn = (ExportDefinition.GetComment)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(ExportDefinition.GetComment)); + IntPtr resultPtr = fn(); + return Marshal.PtrToStringAnsi(resultPtr); + } + public List GetConfiguredAccessTypes() + { + IntPtr procAddress = dllAddressMappings["GetConfiguredAccessTypes"]; + if (procAddress == IntPtr.Zero) + { + return new List(); + } + uint[] accessTypes = new uint[1000]; + var fn = (ExportDefinition.GetConfiguredAccessTypes)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(ExportDefinition.GetConfiguredAccessTypes)); + int accessTypesCount = fn(accessTypes); + return accessTypes.Take(accessTypesCount).ToList(); + } + public int GetSeedLength(uint securityLevel) + { + IntPtr procAddress = dllAddressMappings["GetSeedLength"]; + if (procAddress == IntPtr.Zero) + { + return 0; + } + var fn = (ExportDefinition.GetSeedLength)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(ExportDefinition.GetSeedLength)); + return fn(securityLevel); + } + public int GetKeyLength(uint securityLevel) + { + IntPtr procAddress = dllAddressMappings["GetKeyLength"]; + if (procAddress == IntPtr.Zero) + { + return 0; + } + var fn = (ExportDefinition.GetKeyLength)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(ExportDefinition.GetKeyLength)); + return fn(securityLevel); + } + + private byte[] GenerateKey(byte[] seed, uint securityLevel, bool addOptionParameter, out ExportDefinition.VKeyGenResultEx returnError) + { + returnError = ExportDefinition.VKeyGenResultEx.UnknownError; + + IntPtr procAddress = dllAddressMappings[addOptionParameter ? "GenerateKeyExOpt" : "GenerateKeyEx"]; + if ((!KeyGenerationCapability) || procAddress == IntPtr.Zero) + { + return new byte[] { }; + } + byte[] keyResult = new byte[0x1000]; + uint actualkeySize; + int keygenResult = (int)returnError; + + if (addOptionParameter) + { + var fn = (ExportDefinition.GenerateKeyExOpt)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(ExportDefinition.GenerateKeyExOpt)); + keygenResult = fn(seed, (uint)seed.Length, securityLevel, null, null, keyResult, (uint)keyResult.Length, out actualkeySize); + } + else + { + var fn = (ExportDefinition.GenerateKeyEx)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(ExportDefinition.GenerateKeyEx)); + keygenResult = fn(seed, (uint)seed.Length, securityLevel, null, keyResult, (uint)keyResult.Length, out actualkeySize); + } + returnError = (ExportDefinition.VKeyGenResultEx)keygenResult; + + keyResult = keyResult.Take((int)actualkeySize).ToArray(); + return keyResult; + } + + public void UnloadLibrary() + { + // WARNING: the instance will no longer be able to access native functions after this is called + // This is a workaround if many DLLs have to be enumerated for their metadata -- Windows has a limit on the number of DLLs that can be loaded simultaneously + UnmanagedUtility.FreeLibrary(dllHandle); + } + + ~DllContext() + { + if (dllHandle != IntPtr.Zero) + { + UnmanagedUtility.FreeLibrary(dllHandle); + } + } + } +} + diff --git a/UnlockECU/UnlockECUTests/External/ExportDefinition.cs b/UnlockECU/UnlockECUTests/External/ExportDefinition.cs new file mode 100644 index 0000000..a13bd7d --- /dev/null +++ b/UnlockECU/UnlockECUTests/External/ExportDefinition.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + + +namespace UnlockECUTests +{ + class ExportDefinition + { + public static string[] KnownExportedFunctions = new string[] { "GetECUName", "GetComment", "GetKeyLength", "GetSeedLength", "GetConfiguredAccessTypes", "GenerateKeyExOpt", "GenerateKeyEx" }; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr GetECUName(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr GetComment(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int GetKeyLength(uint iSecurityLevel); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int GetSeedLength(uint iSecurityLevel); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int GetConfiguredAccessTypes(uint[] iSecurityLevels); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int GenerateKeyExOpt(byte[] ipSeedArray, uint iSeedArraySize, uint iSecurityLevel, byte[] ipVariant, byte[] ipOptions, byte[] iopKeyArray, uint iMaxKeyArraySize, out uint oActualKeyArraySize); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int GenerateKeyEx(byte[] ipSeedArray, uint iSeedArraySize, uint iSecurityLevel, byte[] ipVariant, byte[] iopKeyArray, uint iMaxKeyArraySize, out uint oActualKeyArraySize); + + public enum VKeyGenResultEx + { + OK = 0, + BufferTooSmall = 1, + SecurityLevelInvalid = 2, + VariantInvalid = 3, + UnknownError = 4 + } + } +} diff --git a/UnlockECU/UnlockECUTests/External/UnmanagedUtility.cs b/UnlockECU/UnlockECUTests/External/UnmanagedUtility.cs new file mode 100644 index 0000000..8b7dc22 --- /dev/null +++ b/UnlockECU/UnlockECUTests/External/UnmanagedUtility.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECUTests +{ + class UnmanagedUtility + { + /* + Symbol enumeration: + https://stackoverflow.com/questions/18249566/c-sharp-get-the-list-of-unmanaged-c-dll-exports + */ + + [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SymInitialize(IntPtr hProcess, string UserSearchPath, [MarshalAs(UnmanagedType.Bool)] bool fInvadeProcess); + + [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SymCleanup(IntPtr hProcess); + + [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern ulong SymLoadModuleEx(IntPtr hProcess, IntPtr hFile, string ImageName, string ModuleName, long BaseOfDll, int DllSize, IntPtr Data, int Flags); + + [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SymEnumerateSymbols64(IntPtr hProcess, ulong BaseOfDll, SymEnumerateSymbolsProc64 EnumSymbolsCallback, IntPtr UserContext); + + public delegate bool SymEnumerateSymbolsProc64(string SymbolName, ulong SymbolAddress, uint SymbolSize, IntPtr UserContext); + + /* + DLL invocation: + https://stackoverflow.com/questions/16518943/dllimport-or-loadlibrary-for-best-performance + */ + + [DllImport("kernel32.dll")] + public static extern IntPtr LoadLibrary(string dllToLoad); + + [DllImport("kernel32.dll")] + public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); + + [DllImport("kernel32.dll")] + public static extern bool FreeLibrary(IntPtr hModule); + + + // Required for textbox placeholder string + public const int EM_SETCUEBANNER = 0x1501; + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern Int32 SendMessage(IntPtr hWnd, int msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam); + + + private static List LibraryExports = new List(); + + public static bool SymbolEnumeratedCallback(string name, ulong address, uint size, IntPtr context) + { + // Useful for debug: + // Console.WriteLine(name); + LibraryExports.Add(name); + return true; + } + private static bool EnumerateDllExports(string modulePath) + { + IntPtr hCurrentProcess = Process.GetCurrentProcess().Handle; + + ulong dllBase; + + // Initialize symbol handler with our own process handle + if (!SymInitialize(hCurrentProcess, null, false)) + { + Console.WriteLine("SymInitialize function (dbghelp.h) failed"); + return false; + } + + // Load dll + dllBase = SymLoadModuleEx(hCurrentProcess, IntPtr.Zero, modulePath, null, 0, 0, IntPtr.Zero, 0); + + if (dllBase == 0) + { + Console.Out.WriteLine($"Failed to load module: {modulePath}"); + SymCleanup(hCurrentProcess); + return false; + } + + // Clean up the results list before it gets populated + LibraryExports.Clear(); + + // Enumerate symbols. For every symbol, the callback method SymbolEnumeratedCallback is called. + if (SymEnumerateSymbols64(hCurrentProcess, dllBase, SymbolEnumeratedCallback, IntPtr.Zero) == false) + { + Console.Out.WriteLine($"Failed to enumerate symbols for library {modulePath}"); + return false; + } + + SymCleanup(hCurrentProcess); + return true; + } + + public static List GetExports(string modulePath) + { + if (EnumerateDllExports(modulePath)) + { + return LibraryExports; + } + else + { + return new List(); + } + } + + public static void DumpExportsToConsole(string modulePath) + { + List exports = UnmanagedUtility.GetExports(modulePath); + Console.WriteLine($"Retrieving exports for {modulePath}"); + foreach (string s in exports) + { + Console.WriteLine($"{modulePath}: {s}"); + } + Console.WriteLine($"End of {modulePath} exports."); + } + } +} diff --git a/UnlockECU/UnlockECUTests/UnlockECUTests.csproj b/UnlockECU/UnlockECUTests/UnlockECUTests.csproj new file mode 100644 index 0000000..0a3002e --- /dev/null +++ b/UnlockECU/UnlockECUTests/UnlockECUTests.csproj @@ -0,0 +1,23 @@ + + + + net5.0 + + false + + + + x86 + + + + + + + + + + + + + diff --git a/UnlockECU/UnlockECUTests/VerifyWithDLL.cs b/UnlockECU/UnlockECUTests/VerifyWithDLL.cs new file mode 100644 index 0000000..f92ae7a --- /dev/null +++ b/UnlockECU/UnlockECUTests/VerifyWithDLL.cs @@ -0,0 +1,146 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnlockECU; + +namespace UnlockECUTests +{ + public class Tests + { + [SetUp] + public void Setup() + { + + } + + [Test] + public void VerifyOutputWithOfficialDLLs() + { + string dbPath = $"{GetLibraryFolder()}db.json"; + string definitionJson = File.ReadAllText(dbPath); + List definitions = System.Text.Json.JsonSerializer.Deserialize>(definitionJson); + List providers = SecurityProvider.GetSecurityProviders(); + + QuickTest(definitions.Find(x => (x.EcuName == "CRD3S2SEC9A") && (x.AccessLevel == 9) && (x.Provider == "DaimlerStandardSecurityAlgo")), providers); + QuickTest(definitions.Find(x => (x.EcuName == "RBS222") && (x.AccessLevel == 11) && (x.Provider == "DaimlerStandardSecurityAlgoMod")), providers); + QuickTest(definitions.Find(x => (x.EcuName == "IC177") && (x.AccessLevel == 11) && (x.Provider == "DaimlerStandardSecurityAlgoRefG")), providers); + QuickTest(definitions.Find(x => (x.EcuName == "MED40") && (x.AccessLevel == 5) && (x.Provider == "PowertrainSecurityAlgo")), providers); + QuickTest(definitions.Find(x => (x.EcuName == "CR6NFZ") && (x.AccessLevel == 1) && (x.Provider == "PowertrainSecurityAlgo2")), providers); + QuickTest(definitions.Find(x => (x.EcuName == "DCDC223") && (x.AccessLevel == 17) && (x.Provider == "EsLibEd25519")), providers); + + Assert.Pass(); + } + + static string GetLibraryFolder() + { + return $"{Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)}{Path.DirectorySeparatorChar}Library{Path.DirectorySeparatorChar}"; + } + + static void QuickTest(Definition definition, List providers) + { + // Build in x86 to test, else the pinvokes will fail! + + SecurityProvider provider = providers.Find(x => x.GetProviderName() == definition.Provider); + if (provider is null) + { + Console.WriteLine($"Could not find a security provider for {definition.EcuName} ({definition.Provider})"); + Assert.Fail(); + return; + } + + string dllPath = $"{GetLibraryFolder()}{definition.Provider}_L{definition.AccessLevel}.dll"; + Console.WriteLine(dllPath); + if (!File.Exists(dllPath)) + { + Console.WriteLine($"Could not find the target security DLL to verify with. {definition.EcuName} ({definition.Provider})"); + Assert.Fail(); + return; + } + + Console.WriteLine($"Running QuickTest for {definition.EcuName} ({definition.Provider})"); + DllContext context = new DllContext(dllPath, false); + + Tuple match = context.AccessLevels.Find(x => x.Item1 == definition.AccessLevel); + if (match is null) + { + Console.WriteLine($"DLL does not support access level {definition.AccessLevel}"); + Assert.Fail(); + return; + } + + List TestInput = new List(); + foreach (GeneratedByteType byteType in Enum.GetValues(typeof(GeneratedByteType))) + { + TestInput.Add(GenerateBytes(definition.SeedLength, byteType)); + } + + bool matchesAreValid = true; + foreach (byte[] testRow in TestInput) + { + byte[] outKey = new byte[definition.KeyLength]; + if (provider.GenerateKey(testRow, outKey, definition.AccessLevel, definition.Parameters)) + { + byte[] dllResult = context.GenerateKeyAuto((uint)definition.AccessLevel, testRow); + matchesAreValid &= outKey.SequenceEqual(dllResult); + Console.WriteLine($"In: {BitUtility.BytesToHex(testRow)} Out: {BitUtility.BytesToHex(outKey)} DLL: {BitUtility.BytesToHex(dllResult)}"); + } + } + string testResult = matchesAreValid ? "Passed" : "Failed"; + Console.WriteLine($"QuickTest {testResult}"); + if (!matchesAreValid) + { + Assert.Fail(); + } + } + + enum GeneratedByteType + { + Zeroes, + Max, + Ascending, + Descending, + Random, + } + + static byte[] GenerateBytes(int size, GeneratedByteType byteType) + { + byte[] output = new byte[size]; + if (byteType == GeneratedByteType.Zeroes) + { + // do nothing + } + else if (byteType == GeneratedByteType.Max) + { + for (int i = 0; i < size; i++) + { + output[i] = 0xFF; + } + } + else if (byteType == GeneratedByteType.Ascending) + { + for (int i = 0; i < size; i++) + { + output[i] = (byte)i; + } + } + else if (byteType == GeneratedByteType.Descending) + { + for (int i = 0; i < size; i++) + { + output[i] = (byte)(size - 1 - i); + } + } + else if (byteType == GeneratedByteType.Random) + { + Random r = new Random(); + r.NextBytes(output); + } + + return output; + } + + } +} \ No newline at end of file diff --git a/UnlockECU/VisualUnlockECU/MainForm.Designer.cs b/UnlockECU/VisualUnlockECU/MainForm.Designer.cs new file mode 100644 index 0000000..6585667 --- /dev/null +++ b/UnlockECU/VisualUnlockECU/MainForm.Designer.cs @@ -0,0 +1,154 @@ + +namespace VisualUnlockECU +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); + this.dgvMain = new System.Windows.Forms.DataGridView(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.txtFilter = new System.Windows.Forms.TextBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.txtKeyValue = new System.Windows.Forms.TextBox(); + this.txtSeedValue = new System.Windows.Forms.TextBox(); + ((System.ComponentModel.ISupportInitialize)(this.dgvMain)).BeginInit(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.SuspendLayout(); + // + // dgvMain + // + this.dgvMain.AllowUserToAddRows = false; + this.dgvMain.AllowUserToDeleteRows = false; + this.dgvMain.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.dgvMain.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dgvMain.Location = new System.Drawing.Point(12, 12); + this.dgvMain.MultiSelect = false; + this.dgvMain.Name = "dgvMain"; + this.dgvMain.ReadOnly = true; + this.dgvMain.RowTemplate.Height = 25; + this.dgvMain.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; + this.dgvMain.Size = new System.Drawing.Size(1118, 404); + this.dgvMain.TabIndex = 0; + this.dgvMain.SelectionChanged += new System.EventHandler(this.dgvMain_SelectionChanged); + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox1.Controls.Add(this.txtFilter); + this.groupBox1.Location = new System.Drawing.Point(12, 422); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(1118, 56); + this.groupBox1.TabIndex = 1; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Filter"; + // + // txtFilter + // + this.txtFilter.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtFilter.Location = new System.Drawing.Point(6, 22); + this.txtFilter.Name = "txtFilter"; + this.txtFilter.PlaceholderText = "Filter module by name"; + this.txtFilter.Size = new System.Drawing.Size(1106, 23); + this.txtFilter.TabIndex = 0; + this.txtFilter.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + this.txtFilter.TextChanged += new System.EventHandler(this.txtFilter_TextChanged); + // + // groupBox2 + // + this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox2.Controls.Add(this.txtKeyValue); + this.groupBox2.Controls.Add(this.txtSeedValue); + this.groupBox2.Location = new System.Drawing.Point(12, 484); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(1118, 80); + this.groupBox2.TabIndex = 2; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Key Generation"; + // + // txtKeyValue + // + this.txtKeyValue.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtKeyValue.Location = new System.Drawing.Point(6, 48); + this.txtKeyValue.Name = "txtKeyValue"; + this.txtKeyValue.PlaceholderText = "Output seed value (read-only)"; + this.txtKeyValue.ReadOnly = true; + this.txtKeyValue.Size = new System.Drawing.Size(1106, 23); + this.txtKeyValue.TabIndex = 1; + this.txtKeyValue.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + // + // txtSeedValue + // + this.txtSeedValue.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtSeedValue.Location = new System.Drawing.Point(6, 22); + this.txtSeedValue.Name = "txtSeedValue"; + this.txtSeedValue.PlaceholderText = "Enter seed value in hex (e.g. 00 11 22 33)"; + this.txtSeedValue.Size = new System.Drawing.Size(1106, 23); + this.txtSeedValue.TabIndex = 0; + this.txtSeedValue.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + this.txtSeedValue.TextChanged += new System.EventHandler(this.txtSeedValue_TextChanged); + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1142, 576); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.dgvMain); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Name = "MainForm"; + this.Text = "UnlockECU"; + this.Load += new System.EventHandler(this.MainForm_Load); + ((System.ComponentModel.ISupportInitialize)(this.dgvMain)).EndInit(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.DataGridView dgvMain; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TextBox txtFilter; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.TextBox txtKeyValue; + private System.Windows.Forms.TextBox txtSeedValue; + } +} + diff --git a/UnlockECU/VisualUnlockECU/MainForm.cs b/UnlockECU/VisualUnlockECU/MainForm.cs new file mode 100644 index 0000000..3b3128b --- /dev/null +++ b/UnlockECU/VisualUnlockECU/MainForm.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using UnlockECU; + +namespace VisualUnlockECU +{ + public partial class MainForm : Form + { + List Definitions; + public MainForm() + { + string definitionJson = File.ReadAllText("db.json"); + Definitions = System.Text.Json.JsonSerializer.Deserialize>(definitionJson); + + InitializeComponent(); + } + private void MainForm_Load(object sender, EventArgs e) + { + EnableDoubleBuffer(dgvMain, true); + UpdateGrid(); + } + public static void EnableDoubleBuffer(DataGridView dgv, bool setting) + { + Type dgvType = dgv.GetType(); + PropertyInfo pi = dgvType.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic); + pi.SetValue(dgv, setting, null); + } + + private void UpdateGrid(string filter = "") + { + DataTable dt = new DataTable(); + + dt.Columns.Add("Index"); + dt.Columns.Add("Name"); + dt.Columns.Add("Origin"); + dt.Columns.Add("Level"); + dt.Columns.Add("Seed Size"); + dt.Columns.Add("Key Size"); + dt.Columns.Add("Security Provider"); + + List providers = SecurityProvider.GetSecurityProviders(); + for (int i = 0; i < Definitions.Count; i++) + { + Definition definition = Definitions[i]; + + if (providers.Find(x => x.GetProviderName() == definition.Provider) is null) + { + continue; + } + if (!definition.Origin.ToLower().Contains(filter.ToLower())) + { + continue; + } + + dt.Rows.Add(new string[] { + i.ToString(), + definition.EcuName, + definition.Origin, + definition.AccessLevel.ToString(), + definition.SeedLength.ToString(), + definition.KeyLength.ToString(), + definition.Provider + }); + } + + dgvMain.DataSource = dt; + dgvMain.Columns[0].Visible = false; + dgvMain.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; + dgvMain.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; + dgvMain.Columns[3].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells; + dgvMain.Columns[4].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells; + dgvMain.Columns[5].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells; + dgvMain.Columns[6].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; + } + + private void txtFilter_TextChanged(object sender, EventArgs e) + { + UpdateGrid(txtFilter.Text); + } + + private void txtSeedValue_TextChanged(object sender, EventArgs e) + { + TryRefreshKey(); + } + private void dgvMain_SelectionChanged(object sender, EventArgs e) + { + TryRefreshKey(); + } + + public void TryRefreshKey() + { + bool validHex = true; + string cleanedText = txtSeedValue.Text.Replace(" ", "").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace("-", "").ToUpper(); + if (cleanedText.Length % 2 != 0) + { + validHex = false; + } + if (!System.Text.RegularExpressions.Regex.IsMatch(cleanedText, @"\A\b[0-9a-fA-F]+\b\Z")) + { + validHex = false; + } + + if (validHex) + { + byte[] seed = BitUtility.BytesFromHex(cleanedText); + txtSeedValue.BackColor = System.Drawing.SystemColors.Window; + TryGenerateKey(seed); + } + else + { + if (cleanedText.Length == 0) + { + TryGenerateKey(new byte[] { }); + } + txtSeedValue.BackColor = System.Drawing.Color.LavenderBlush; + } + } + + public void TryGenerateKey(byte[] inByte) + { + if (dgvMain.SelectedRows.Count != 1) + { + txtKeyValue.Text = "Please select a definition first"; + return; + } + int selectedIndex = int.Parse(dgvMain.SelectedRows[0].Cells[0].Value.ToString()); + Definition definition = Definitions[selectedIndex]; + + groupBox2.Text = $"Key Generation ({definition})"; + + if (definition.SeedLength != inByte.Length) + { + txtKeyValue.Text = $"Expecting a {definition.SeedLength}-byte seed. Current length is {inByte.Length}"; + return; + } + + SecurityProvider provider = SecurityProvider.GetSecurityProviders().Find(x => x.GetProviderName() == definition.Provider); + + if (provider is null) + { + txtKeyValue.Text = $"Could not load security provider for {definition.Provider}"; + return; + } + byte[] outKey = new byte[definition.KeyLength]; + + if (provider.GenerateKey(inByte, outKey, definition.AccessLevel, definition.Parameters)) + { + txtKeyValue.Text = BitUtility.BytesToHex(outKey, true); + } + else + { + txtKeyValue.Text = $"Key generation was unsuccessful ({definition.Provider})"; + return; + } + } + + } +} diff --git a/UnlockECU/VisualUnlockECU/MainForm.resx b/UnlockECU/VisualUnlockECU/MainForm.resx new file mode 100644 index 0000000..a0b1d3b --- /dev/null +++ b/UnlockECU/VisualUnlockECU/MainForm.resx @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAA + AAA+ncp7NpbR5TOQzOsyi8vtO5XChwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAQqzhzcTr9/9/4fb/n+b3/zKKye86kLqRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAESw4//G9Pv/Q9bx/0jb9f+C4fX/MYjI8DmNt5gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABEsOPyu+/6/znR8f8oxe7/Ttz2/4Xi9/8yi8ruOYu1mwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAARLDj//D8/v+w7vr/Q9j0/yjI7v9B1/T/ieL3/zKLy+04iLKjAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAESw45ZEsOP/RLDj/6vq+f9O2PP/K8nv/z3W8/+K4ff/MorK7jOD + uNwpfdb/LIXY/zaOwcsAAAAAAAAAAAAAAAAAAAAAAAAAAESw4//x/P7/u/H7/3vk9v8o0vD/N9T1/4Pg + 9v8+qeP/oPP8/6n1/P8rgtf/NYu/zwAAAAAAAAAAAAAAAAAAAABEsOOWRLDj/0Wy4/92xer/rO76/znW + 8v9N2/X/ZeT3/zzO8v8yye//he/7/yuB1/81iLvUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRLDjlm/E + 6v+A5ff/PdHx/13b9f9p3/b/UNfz/zTN7/+F7/v/KX/W/zSGutgAAAAAAAAAAAAAAAAAAAAAAAAAAESw + 4//V9/z/ief4/37k9/9+5Pf/fuT3/4Ll9/9H1vL/OM7w/671/P8pfNb/AAAAAAAAAAAAAAAAAAAAAAAA + AABEsOP/vvL7/37k9/9+5Pf/geX3/5Tp+P+88fv/i9rz/0nd9f/B+P3/MJDa/wAAAAAAAAAAAAAAAAAA + AAAAAAAARLDj/974/P+N5/j/fuT3/5Tp+P+86fj/RLDj/0Ks4//u/P7/Mpjd/zmVyL8AAAAAAAAAAAAA + AAAAAAAAAAAAAESw45ZEsOP/zvX8/43n+P+h7Pn/RLDj/0Sw4///////OaHf/zmVyL8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAARLDjlkSw4//O9fz/nuv5/77y+//+////RLDj/0Kr35wAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEsOOWRLDj/974/P/e+Pz/RLDj/0Sw45YAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAESw45ZEsOP/RLDj/0Sw45YAAAAAAAAAAAAA + AAAAAAAAB/8AAAP/AAAB/wAAAP8AAAB/AAAABwAAwAMAAMABAAD4AAAA+AAAAPgAAAD4AAAA+AEAAPwD + AAD+BwAA/w8AAA== + + + \ No newline at end of file diff --git a/UnlockECU/VisualUnlockECU/Program.cs b/UnlockECU/VisualUnlockECU/Program.cs new file mode 100644 index 0000000..9cf14d1 --- /dev/null +++ b/UnlockECU/VisualUnlockECU/Program.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace VisualUnlockECU +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/UnlockECU/VisualUnlockECU/VisualUnlockECU.csproj b/UnlockECU/VisualUnlockECU/VisualUnlockECU.csproj new file mode 100644 index 0000000..a2f15c6 --- /dev/null +++ b/UnlockECU/VisualUnlockECU/VisualUnlockECU.csproj @@ -0,0 +1,14 @@ + + + + WinExe + net5.0-windows + true + key.ico + + + + + + + \ No newline at end of file diff --git a/UnlockECU/VisualUnlockECU/key.ico b/UnlockECU/VisualUnlockECU/key.ico new file mode 100644 index 0000000..2aec303 Binary files /dev/null and b/UnlockECU/VisualUnlockECU/key.ico differ diff --git a/docs/resources/demo-thumb.png b/docs/resources/demo-thumb.png new file mode 100644 index 0000000..f78d4e0 Binary files /dev/null and b/docs/resources/demo-thumb.png differ diff --git a/docs/resources/demo.mp4 b/docs/resources/demo.mp4 new file mode 100644 index 0000000..6344def Binary files /dev/null and b/docs/resources/demo.mp4 differ diff --git a/docs/resources/header.png b/docs/resources/header.png new file mode 100644 index 0000000..af3d519 Binary files /dev/null and b/docs/resources/header.png differ