From 2c8b4ea9cc10e126b6b0f1b6c65d5c0a2027d018 Mon Sep 17 00:00:00 2001 From: JinGen Lim <1116555+jglim@users.noreply.github.com> Date: Tue, 1 Dec 2020 00:06:47 +0800 Subject: [PATCH] Add @bri3d 's SA2 implementation No actual definitions added, only a security provider based on @bri3d 's repo at from https://github.com/bri3d/sa2_seed_key --- UnlockECU/UnlockECU/Program.cs | 6 +- UnlockECU/UnlockECU/Security/VolkswagenSA2.cs | 187 ++++++++++++++++++ UnlockECU/UnlockECUTests/VerifyWithDLL.cs | 27 +++ .../VisualUnlockECU/VisualUnlockECU.csproj | 2 +- 4 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 UnlockECU/UnlockECU/Security/VolkswagenSA2.cs diff --git a/UnlockECU/UnlockECU/Program.cs b/UnlockECU/UnlockECU/Program.cs index 2b631e3..60cf849 100644 --- a/UnlockECU/UnlockECU/Program.cs +++ b/UnlockECU/UnlockECU/Program.cs @@ -17,11 +17,10 @@ namespace UnlockECU 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; @@ -66,5 +65,8 @@ namespace UnlockECU } return result; } + */ + } + } diff --git a/UnlockECU/UnlockECU/Security/VolkswagenSA2.cs b/UnlockECU/UnlockECU/Security/VolkswagenSA2.cs new file mode 100644 index 0000000..0d41776 --- /dev/null +++ b/UnlockECU/UnlockECU/Security/VolkswagenSA2.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnlockECU +{ + /// + /// Volkswagen SA2 implementation from https://github.com/bri3d/sa2_seed_key + /// + class VolkswagenSA2 : SecurityProvider + { + public override bool GenerateKey(byte[] inSeed, byte[] outKey, int accessLevel, List parameters) + { + byte[] tape = GetParameterBytearray(parameters, "InstructionTape"); + + if ((inSeed.Length != 4) || (outKey.Length != 4)) + { + return false; + } + + SA2SeedKey sk = new SA2SeedKey(tape, BytesToInt(inSeed, Endian.Big, 0)); + IntToBytes(sk.Execute(), outKey, Endian.Big); + return true; + } + + public override string GetProviderName() + { + return "VolkswagenSA2"; + } + } + + + public class SA2SeedKey + { + uint Register = 0; + uint CarryFlag = 0; + byte[] InstructionTape = Array.Empty(); + uint InstructionPointer = 0; + List ForPointers = new List(); + List ForIterations = new List(); + + public SA2SeedKey(byte[] tape, uint seed) + { + InstructionTape = tape; + Register = seed; + } + + private void RegisterShiftLeft() + { + CarryFlag = Register & 0x80000000; + Register <<= 1; + if (CarryFlag > 0) + { + Register |= 1; + } + InstructionPointer++; + } + + private void RegisterShiftRight() + { + CarryFlag = Register & 1; + Register >>= 1; + if (CarryFlag > 0) + { + Register |= 0x80000000; + } + InstructionPointer++; + } + + private void Add() + { + CarryFlag = 0; + long outputRegister = Register; + outputRegister += FetchOperand(); + if (outputRegister > 0xFFFFFFFF) + { + CarryFlag = 1; + outputRegister &= 0xFFFFFFFF; + } + Register = (uint)outputRegister; + InstructionPointer += 5; + } + + private void Subtract() + { + CarryFlag = 0; + long outputRegister = Register; + outputRegister -= FetchOperand(); + if (outputRegister > 0xFFFFFFFF) + { + CarryFlag = 1; + outputRegister &= 0xFFFFFFFF; + } + Register = (uint)outputRegister; + InstructionPointer += 5; + } + + private void ExclusiveOr() + { + uint op = FetchOperand(); + Register ^= op; + InstructionPointer += 5; + } + + private void LoopInit() + { + ForIterations.Insert(0, FetchOperandByte() - 1); + InstructionPointer += 2; + ForPointers.Insert(0, InstructionPointer); + } + + private void LoopNext() + { + if (ForIterations[0] > 0) + { + ForIterations[0]--; + InstructionPointer = ForPointers[0]; + } + else + { + ForIterations.RemoveAt(0); + ForPointers.RemoveAt(0); + InstructionPointer++; + } + } + + private void BranchConditional() + { + uint skipCount = (uint)FetchOperandByte() + 2; + if (CarryFlag == 0) + { + InstructionPointer += skipCount; + } + else + { + InstructionPointer += 2; + } + } + + private void BranchUnconditional() + { + InstructionPointer += (uint)FetchOperandByte() + 2; + } + + private void EndExecution() + { + InstructionPointer++; + } + + private uint FetchOperand() + { + byte[] operands = InstructionTape.Skip((int)InstructionPointer + 1).Take(4).ToArray(); + uint interpretedInt = (uint)operands[0] << 24 | (uint)operands[1] << 16 | (uint)operands[2] << 8 | (uint)operands[3]; + return interpretedInt; + } + private byte FetchOperandByte() + { + return InstructionTape[InstructionPointer + 1]; + } + + public uint Execute() + { + Dictionary instructionSet = new Dictionary(); + instructionSet.Add(0x81, RegisterShiftLeft); + instructionSet.Add(0x82, RegisterShiftRight); + instructionSet.Add(0x93, Add); + instructionSet.Add(0x84, Subtract); + instructionSet.Add(0x87, ExclusiveOr); + instructionSet.Add(0x68, LoopInit); + instructionSet.Add(0x49, LoopNext); + instructionSet.Add(0x4A, BranchConditional); + instructionSet.Add(0x6B, BranchUnconditional); + instructionSet.Add(0x4C, EndExecution); + + while (InstructionPointer < InstructionTape.Length) + { + // Uncomment to trace execution + // Console.WriteLine($"IP: 0x{InstructionPointer:X} Reg: 0x{Register:X8} CF: 0x{CarryFlag:X8} Op: {instructionSet[InstructionTape[InstructionPointer]].Method.Name} "); + instructionSet[InstructionTape[InstructionPointer]](); + } + Console.WriteLine($"Exec done, reg = 0x{Register:X8}"); + return Register; + } + } +} diff --git a/UnlockECU/UnlockECUTests/VerifyWithDLL.cs b/UnlockECU/UnlockECUTests/VerifyWithDLL.cs index f92ae7a..05eaef9 100644 --- a/UnlockECU/UnlockECUTests/VerifyWithDLL.cs +++ b/UnlockECU/UnlockECUTests/VerifyWithDLL.cs @@ -31,9 +31,36 @@ namespace UnlockECUTests 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); + VWTest(definitions, providers); + Assert.Pass(); } + static void VWTest(List definitions, List providers) + { + SecurityProvider vwProvider = providers.Find(x => x.GetProviderName() == "VolkswagenSA2"); + Definition vw1 = definitions.Find(x => x.EcuName == "VW_SA2_TEST1"); + Definition vw2 = definitions.Find(x => x.EcuName == "VW_SA2_TEST2"); + byte[] vwKey = new byte[4]; + byte[] expectedOutput; + + expectedOutput = BitUtility.BytesFromHex("3C7876D8"); + vwProvider.GenerateKey(BitUtility.BytesFromHex("A04EB1ED"), vwKey, 1, vw1.Parameters); // 0x3C7876D8 + + if (!vwKey.SequenceEqual(expectedOutput)) + { + Assert.Fail(); + } + + expectedOutput = BitUtility.BytesFromHex("6A37F02E"); + vwProvider.GenerateKey(BitUtility.BytesFromHex("1A1B1C1D"), vwKey, 1, vw2.Parameters); // 0x6a37f02e + + if (!vwKey.SequenceEqual(expectedOutput)) + { + Assert.Fail(); + } + } + static string GetLibraryFolder() { return $"{Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)}{Path.DirectorySeparatorChar}Library{Path.DirectorySeparatorChar}"; diff --git a/UnlockECU/VisualUnlockECU/VisualUnlockECU.csproj b/UnlockECU/VisualUnlockECU/VisualUnlockECU.csproj index a2f15c6..afa82ca 100644 --- a/UnlockECU/VisualUnlockECU/VisualUnlockECU.csproj +++ b/UnlockECU/VisualUnlockECU/VisualUnlockECU.csproj @@ -1,7 +1,7 @@  - WinExe + Exe net5.0-windows true key.ico