From e0f967341e41f0c78fdc29f0acaa5bfaf86a7b98 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Thu, 23 Nov 2023 23:34:31 +0800 Subject: [PATCH] TCG decrypt [skip ci] --- .../BaseClassLibrary/JsonSerializeTest.cs | 4 +- .../GeniusInvokationDecoding.cs | 218 ++++++++++++++++++ 2 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao.Test/IncomingFeature/GeniusInvokationDecoding.cs diff --git a/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/JsonSerializeTest.cs b/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/JsonSerializeTest.cs index f680883f..40dd8c3a 100644 --- a/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/JsonSerializeTest.cs +++ b/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/JsonSerializeTest.cs @@ -7,9 +7,7 @@ namespace Snap.Hutao.Test.BaseClassLibrary; [TestClass] public class JsonSerializeTest { - private TestContext? testContext; - - public TestContext? TestContext { get => testContext; set => testContext = value; } + public TestContext? TestContext { get; set; } private readonly JsonSerializerOptions AlowStringNumberOptions = new() { diff --git a/src/Snap.Hutao/Snap.Hutao.Test/IncomingFeature/GeniusInvokationDecoding.cs b/src/Snap.Hutao/Snap.Hutao.Test/IncomingFeature/GeniusInvokationDecoding.cs new file mode 100644 index 00000000..7307d894 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao.Test/IncomingFeature/GeniusInvokationDecoding.cs @@ -0,0 +1,218 @@ +using System; +using System.Buffers.Binary; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Text; + +namespace Snap.Hutao.Test.IncomingFeature; + +[TestClass] +public sealed class GeniusInvokationDecoding +{ + public TestContext? TestContext { get; set; } + + /// + /// https://www.bilibili.com/video/av278125720 + /// + [TestMethod] + public unsafe void GeniusInvokationShareCodeDecoding() + { + // 51 bytes obfuscated data + byte[] bytes = Convert.FromBase64String("BCHBwxQNAYERyVANCJGBynkOCZER2pgOCrFx8poQChGR9bYQDEGB9rkQDFKRD7oRDeEB"); + + // --------------------------------------------- + // | Data | Caesar Cipher Key | + // |----------|-------------------| + // | 50 Bytes | 1 Byte | + // --------------------------------------------- + // Data: + // 00000100 00100001 11000001 11000011 00010100 + // 00001101 00000001 10000001 00010001 11001001 + // 01010000 00001101 00001000 10010001 10000001 + // 11001010 01111001 00001110 00001001 10010001 + // 00010001 11011010 10011000 00001110 00001010 + // 10110001 01110001 11110010 10011010 00010000 + // 00001010 00010001 10010001 11110101 10110110 + // 00010000 00001100 01000001 10000001 11110110 + // 10111001 00010000 00001100 01010010 10010001 + // 00001111 10111010 00010001 00001101 11100001 + // --------------------------------------------- + // Caesar Cipher Key: + // 00000001 + // --------------------------------------------- + fixed (byte* ptr = bytes) + { + // Reinterpret as 50 byte actual data and 1 deobfuscate key byte + EncryptedDataAndKey* data = (EncryptedDataAndKey*)ptr; + byte* dataPtr = data->Data; + + // ---------------------------------------------------------- + // | First | Second | Padding | + // |-----------|----------|---------| + // | 25 Bytes | 25 Bytes | 1 Byte | + // ---------------------------------------------------------- + // We are doing two things here: + // 1. Retrieve actual data by subtracting key + // 2. Store data into two halves by alternating between them + // ---------------------------------------------------------- + // What we will get after this step: + // ---------------------------------------------------------- + // First: + // 00000011 11000000 00010011 00000000 00010000 + // 01001111 00000111 10000000 01111000 00001000 + // 00010000 10010111 00001001 01110000 10011001 + // 00001001 10010000 10110101 00001011 10000000 + // 10111000 00001011 10010000 10111001 00001100 + // ---------------------------------------------------------- + // Second: + // 00100000 11000010 00001100 10000000 11001000 + // 00001100 10010000 11001001 00001101 10010000 + // 11011001 00001101 10110000 11110001 00001111 + // 00010000 11110100 00001111 01000000 11110101 + // 00001111 01010001 00001110 00010000 11100000 + // ---------------------------------------------------------- + RearrangeBuffer rearranged = default; + byte* pFirst = rearranged.First; + byte* pSecond = rearranged.Second; + for (int i = 0; i < 50; i++) + { + // Determine which half are we going to insert + byte** ppTarget = i % 2 == 0 ? &pFirst : &pSecond; + + // (actual data = data - key) and store it directly to the target half + **ppTarget = unchecked((byte)(dataPtr[i] - data->Key)); + + (*ppTarget)++; + } + + // Prepare decoded data result storage + DecryptedData decoded = default; + ushort* pDecoded = decoded.Data; + + // ---------------------------------------------------------- + // | Data | + // |----------| x 17 = 51 Bytes + // | 3 Bytes | + // ---------------------------------------------------------- + // Grouping each 3 bytes and read out as 2 ushort with + // 12 bits each (Big Endian) + // ---------------------------------------------------------- + // 00000011 1100·0000 00010011| + // 00000000 0001·0000 01001111| + // 00000111 1000·0000 01111000| + // 00001000 0001·0000 10010111| + // 00001001 0111·0000 10011001| + // 00001001 1001·0000 10110101| + // 00001011 1000·0000 10111000| + // 00001011 1001·0000 10111001| + // 00001100 0010·0000 11000010| + // 00001100 1000·0000 11001000| + // 00001100 1001·0000 11001001| + // 00001101 1001·0000 11011001| + // 00001101 1011·0000 11110001| + // 00001111 0001·0000 11110100| + // 00001111 0100·0000 11110101| + // 00001111 0101·0001 00001110| + // 00010000 1110·0000 -padding|[padding32] + // ---------------------------------------------------------- + // reinterpret as DecodeGroupingHelper for each 3 bytes + DecodeGroupingHelper* pGroup = (DecodeGroupingHelper*)&rearranged; + for (int i = 0; i < 17; i++) + { + (ushort first, ushort second) = pGroup->GetData(); + + *pDecoded = first; + *(pDecoded + 1) = second; + + pDecoded += 2; + pGroup++; + } + + // Now we get + // 60, 19, 1, + // 79,120,120, + // 129,151,151, + // 153,153,181, + // 184,184,185, + // 185,194,194, + // 200,200,201, + // 201,217,217, + // 219,241,241, + // 244,244,245, + // 245,270,270, + StringBuilder stringBuilder = new(); + for (int i = 0; i < 33; i++) + { + stringBuilder + .AppendFormat(CultureInfo.InvariantCulture, "{0,3}", decoded.Data[i]) + .Append(','); + + if (i % 11 == 10) + { + stringBuilder.Append('\n'); + } + } + + TestContext?.WriteLine(stringBuilder.ToString(0, stringBuilder.Length - 1)); + + ushort[] resultArray = new ushort[33]; + Span result = new((ushort*)&decoded, 33); + result.CopyTo(resultArray); + + ushort[] testKnownResult = +#if NET8_0_OR_GREATER + [ + 060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153, + 181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201, + 217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270, + ]; +#else + { + 060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153, + 181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201, + 217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270, + }; +#endif + + CollectionAssert.AreEqual(resultArray, testKnownResult); + } + } + + private struct EncryptedDataAndKey + { + public unsafe fixed byte Data[50]; + public byte Key; + } + + private struct RearrangeBuffer + { + public unsafe fixed byte First[25]; + public unsafe fixed byte Second[25]; + + // Make it 51 bytes + // allow to be group as 17 DecodeGroupingHelper later + public byte padding; + + // prevent accidently int32 cast access violation + public byte paddingTo32; + } + + private struct DecodeGroupingHelper + { + public unsafe fixed byte Data[3]; + + public unsafe (ushort First, ushort Second) GetData() + { + fixed (byte* ptr = Data) + { + uint value = BinaryPrimitives.ReverseEndianness((*(uint*)ptr) & 0x00FFFFFF) >> 8; // keep low 24 bits only + return ((ushort)((value >> 12) & 0x0FFF), (ushort)(value & 0x0FFF)); + } + } + } + + private struct DecryptedData + { + public unsafe fixed ushort Data[33]; + } +} \ No newline at end of file