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