From 5dd8491d139561653b4ffeecfb513f81d53f5c4b Mon Sep 17 00:00:00 2001 From: Mino Date: Sat, 28 Mar 2020 18:02:13 +0900 Subject: [PATCH] Impl encrypt --- Dalamud.Bootstrap/Bootstrapper.cs | 27 ++-- Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs | 5 + .../SqexArg/EncryptedArgument.cs | 148 +++++++++++++++++- .../SqexArg/EncryptedArgumentExtensions.cs | 81 ---------- 4 files changed, 161 insertions(+), 100 deletions(-) delete mode 100644 Dalamud.Bootstrap/SqexArg/EncryptedArgumentExtensions.cs diff --git a/Dalamud.Bootstrap/Bootstrapper.cs b/Dalamud.Bootstrap/Bootstrapper.cs index f6eec5abc..f39959123 100644 --- a/Dalamud.Bootstrap/Bootstrapper.cs +++ b/Dalamud.Bootstrap/Bootstrapper.cs @@ -56,26 +56,19 @@ namespace Dalamud.Bootstrap var argument = ReadArgumentFromProcess(process); - var newTick = Environment.TickCount; + var newTick = (uint)Environment.TickCount; var newKey = newTick & 0xFFFF_0000; // only the high nibble is used - var newArgument = argument.Remove("T") + var newArgument = argument + .Remove("T") .Add("T", $"{newTick}") .ToString(); - // TODO: encode as a encrypted? - // TODO: launch it + var encryptedArgument = new EncryptedArgument(newArgument, newKey); + + + // TODO: launch new exe with the argument from encryptedArgument.ToString() - //var newCommandLine = - // TODO: - // .... if arg1 exists - // DecodeSqexArg(arguments[1]); - // args = ParseArgument() - // FindArguments(args, "T") - // RemoveArgs(args, "T") - // AddArgs(args, "T", newTick) - // str = ToString() - // EncodeSqexArg(str, newKey) process.Terminate(); } @@ -111,7 +104,7 @@ namespace Dalamud.Bootstrap argument = encryptedArgument.Decrypt(key); } - return ArgumentBuilder.TryParse(argument); + return ArgumentBuilder.Parse(argument); } /// @@ -143,11 +136,11 @@ namespace Dalamud.Bootstrap } catch (Exception ex) { - const string message = "Failed to inject Dalamud library into the process."; + var exMessage = $"Failed to inject Dalamud library into the process id {pid}."; // Could not inject Dalamud for whatever reason; it could be process is not actually running, insufficient os privilege, or whatever the thing SE put in their game; // Therefore there's not much we can do on this side; You have to trobleshoot by yourself somehow. - throw new ProcessException(message, pid, ex); + throw new BootstrapException(exMessage, ex); } } } diff --git a/Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs b/Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs index 954858ecc..fa5193064 100644 --- a/Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs +++ b/Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs @@ -12,6 +12,11 @@ namespace Dalamud.Bootstrap.SqexArg m_dict = new Dictionary(); } + public static ArgumentBuilder Parse(string argument) + { + + } + public ArgumentBuilder Add(string key, string value) { m_dict.Add(key, value); diff --git a/Dalamud.Bootstrap/SqexArg/EncryptedArgument.cs b/Dalamud.Bootstrap/SqexArg/EncryptedArgument.cs index d77948826..36706d4e0 100644 --- a/Dalamud.Bootstrap/SqexArg/EncryptedArgument.cs +++ b/Dalamud.Bootstrap/SqexArg/EncryptedArgument.cs @@ -1,9 +1,20 @@ +using System; +using System.Buffers; +using System.Buffers.Text; +using System.Text; using System.Text.RegularExpressions; +using Dalamud.Bootstrap.Crypto; namespace Dalamud.Bootstrap.SqexArg { internal sealed class EncryptedArgument { + private static readonly char[] ChecksumTable = + { + 'f', 'X', '1', 'p', 'G', 't', 'd', 'S', + '5', 'C', 'A', 'P', '4', '_', 'V', 'L' + }; + /// /// A data that is encrypted and encoded in url-safe variant of base64. /// @@ -14,12 +25,127 @@ namespace Dalamud.Bootstrap.SqexArg /// public char Checksum { get; } - public EncryptedArgument(string data, char checksum) + /// + /// Creates an encrypted argument. + /// Unlike other constructors, this does not encrypt the data passed to encdoedData parameter and assume that process is already done. + /// + /// A string that is already encrypted and encoded to url-safe variant of base64. + /// A checksum that is used to validate the encryption key. + private EncryptedArgument(string encodedData, char checksum) { - Data = data; + Data = encodedData; Checksum = checksum; } + /// + /// Encrypts a string with given key. + /// + /// A data that is not encrypted. + /// A key that is used to encrypt the data. + public EncryptedArgument(string plainText, uint key) + { + Span keyBytes = stackalloc byte[8]; + CreateKey(key, keyBytes); + + var blowfish = new Blowfish(keyBytes); + + Data = EncodeString(plainText, blowfish); + Checksum = GetChecksum(key); + } + + private static char GetChecksum(uint key) + { + // There's no OoB since ChecksumTable has 16 elements and we mask the key with 0xF + var index = (key >> 16) & 0x0000_000F; + + return ChecksumTable[index]; + } + + /// + /// + /// + /// + /// + /// + /// Thrown when the data property does not have a valid base64 string. + public string Decrypt(uint key) + { + Span keyBytes = stackalloc byte[8]; + CreateKey(key, keyBytes); + + var blowfish = new Blowfish(keyBytes); + + return DecodeString(Data, blowfish); + } + + /// + /// + /// + /// + /// + /// + /// Thrown when the data property does not have a valid base64 string. + private static string DecodeString(string payload, Blowfish blowfish) + { + // plainText <- utf8Bytes <- encryptedBytes(payload) <- base64(payloadStr) + + // base64: 3 bytes per 4 characters + // We also want the size to be aligned with the block size. + var dataLength = (payload.Length / 4) * 3; + var alignedDataLength = AlignBufferLength(dataLength); + var encryptedData = new byte[alignedDataLength]; + var decryptedData = new byte[alignedDataLength]; + + // Converts to standard base64 string so that we can feed it to stdlib + var base64Str = payload + .Replace('-', '+') + .Replace('_', '/'); + + // base64 -> encryptedBytes + if (!Convert.TryFromBase64String(base64Str, encryptedData, out var _)) + { + // We don't care about bytesWritten because we can't handle failure anyway + throw new SqexArgException($"A payload {payload} does not look like a valid encrypted argument."); + } + + // encryptedBytes -> utf8Bytes (decrypted) + blowfish.Decrypt(encryptedData, decryptedData); + + // utf8Bytes -> C# string + var plainText = Encoding.UTF8.GetString(decryptedData[..dataLength]); + + return plainText; + } + + /// + /// Converts plain text string to url-safe variant of base64 string. + /// + private static string EncodeString(string plainText, Blowfish blowfish) + { + // plainText -> utf8Bytes -> encryptedBytes(payload) -> base64(payloadStr) + + // This is needed because we want the size to be aligned with the block size. We'll also need to pad them zero. + var utf8BytesLength = AlignBufferLength(Encoding.UTF8.GetByteCount(plainText)); + var utf8Bytes = new byte[utf8BytesLength]; + + // We also need the buffer to store encrypted bytes + var encryptedBytes = new byte[utf8Bytes.Length]; + + // Now we can the string to UTF8 + // NOTE: This should fail as GetByteCount returns the exact size required to encode utf8 string, but if this assumption is wrong, please make an issue. + Encoding.UTF8.GetBytes(plainText, utf8Bytes); + + // Encrypt it + blowfish.Encrypt(utf8Bytes, encryptedBytes); + + // Convert to url-safe variant of base64 + var base64Str = Convert.ToBase64String(encryptedBytes, Base64FormattingOptions.None) + .Replace('+', '-') + .Replace('/', '_'); + + return base64Str; + } + /// /// /// @@ -65,5 +191,23 @@ namespace Dalamud.Bootstrap.SqexArg } public override string ToString() => $"//**sqex0003{Data}{Checksum}**//"; + + /// + /// Formats a key. + /// + /// A secret key. + /// A buffer where formatted key will be stored. This must be larger than 8 bytes. + internal static void CreateKey(uint key, Span destination) + { + if (!Utf8Formatter.TryFormat(key, destination, out var _, new StandardFormat('x', 8))) + { + throw new InvalidOperationException("BUG: Could not create a key"); + } + } + + /// + /// Rounds to next mutliple of block size (i.e. 8 bytes) to satisfy with Blowfish requirements. + /// + internal static int AlignBufferLength(int length) => (length + (Blowfish.BlockSize - 1)) & (-Blowfish.BlockSize); } } diff --git a/Dalamud.Bootstrap/SqexArg/EncryptedArgumentExtensions.cs b/Dalamud.Bootstrap/SqexArg/EncryptedArgumentExtensions.cs deleted file mode 100644 index 8b580c7fc..000000000 --- a/Dalamud.Bootstrap/SqexArg/EncryptedArgumentExtensions.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Buffers; -using System.Buffers.Text; -using System.Text; -using Dalamud.Bootstrap.Crypto; - -namespace Dalamud.Bootstrap.SqexArg -{ - internal static class EncryptedArgumentExtensions - { - /// - /// - /// - /// - /// - /// - /// Thrown when the data property does not have a valid base64 string. - public static string Decrypt(this EncryptedArgument argument, uint key) - { - Span keyBytes = stackalloc byte[8]; - CreateKey(key, keyBytes); - - var encryptedData = DecodeDataString(argument.Data, out var encryptedDataLength); - var decryptedData = new byte[encryptedData.Length]; - - var blowfish = new Blowfish(keyBytes); - blowfish.Decrypt(encryptedData, decryptedData); - - return Encoding.UTF8.GetString(decryptedData[..encryptedDataLength]); - } - - /// - /// Formats a key. - /// - /// A secret key. - /// A buffer where formatted key will be stored. This must be larger than 8 bytes. - private static void CreateKey(uint key, Span destination) - { - if (!Utf8Formatter.TryFormat(key, destination, out var _, new StandardFormat('x', 8))) - { - throw new InvalidOperationException("BUG: Could not create a key"); - } - } - - /// - /// Converts the data string to bytes. It also allocates more bytes than actual data contained in base64 string for Blowfish. - /// - /// A url-safe variant of base64 string. - /// A data length that is actually written to the buffer. - private static byte[] DecodeDataString(string payload, out int dataLength) - { - var base64Str = payload - .Replace('-', '+') - .Replace('_', '/'); - - // base64: 3 bytes per 4 characters - dataLength = (payload.Length / 4) * 3; - - // round to next mutliple of block size which is what Blowfish can process. (i.e. 8 bytes) - var alignedLength = (dataLength + (Blowfish.BlockSize - 1)) & (-Blowfish.BlockSize); - - var buffer = new byte[alignedLength]; - - if (!Convert.TryFromBase64String(base64Str, buffer, out var _)) - { - throw new SqexArgException($"A payload {payload} does not look like a valid encrypted argument."); - } - - return buffer; - } - - private static string EncodeUrlSafeBase64(byte[] payload) - { - var payloadStr = Convert.ToBase64String(payload); - - return payloadStr - .Replace('+', '-') - .Replace('/', '_'); - } - } -}