diff --git a/Dalamud.Bootstrap/Bootstrapper.cs b/Dalamud.Bootstrap/Bootstrapper.cs index a9f63297d..c4ccd48c6 100644 --- a/Dalamud.Bootstrap/Bootstrapper.cs +++ b/Dalamud.Bootstrap/Bootstrapper.cs @@ -24,6 +24,13 @@ namespace Dalamud.Bootstrap throw new NotImplementedException("TODO"); } + /// + /// + /// + /// + /// + /// Thrown when it could not relaunch FINAL FANTASY XIV or inject Dalamud. + /// public void Relaunch(uint pid) { // TODO @@ -48,10 +55,11 @@ namespace Dalamud.Bootstrap using var process = Process.Open(pid); var commandLine = process.ReadCommandLine(); - if (!EncodedArgument.Parse(commandLine[1], out var container)) - { + // Recover the key - } + // TODO: check if contains arg[1] + + if EncryptedArgument.Extract(commandLine[1], ) // TODO: @@ -67,6 +75,20 @@ namespace Dalamud.Bootstrap process.Terminate(); } + private static uint RecoverKey(Process gameProcess) + { + var createdTime = gameProcess.GetCreationTime(); + + var currentDt = DateTime.Now; + var currentTick = Environment.TickCount; + + var delta = currentDt - createdTime; + var createdTick = (uint)currentTick - (uint)delta.TotalMilliseconds; + + // only the high nibble is used. + return createdTick & 0xFFFF_0000; + } + /// /// Injects Dalamud into the process. See remarks for process state prerequisites. /// diff --git a/Dalamud.Bootstrap/SqexArg/EncodedArgument.cs b/Dalamud.Bootstrap/SqexArg/EncodedArgument.cs deleted file mode 100644 index 32be71571..000000000 --- a/Dalamud.Bootstrap/SqexArg/EncodedArgument.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using System.Buffers; -using System.Buffers.Text; -using System.Runtime.InteropServices; -using System.Text; -using Dalamud.Bootstrap.Crypto; - -namespace Dalamud.Bootstrap.SqexArg -{ - internal sealed class EncodedArgument : IDisposable - { - private static char[] ChecksumTable = new char[] - { - 'f', 'X', '1', 'p', 'G', 't', 'd', 'S', - '5', 'C', 'A', 'P', '4', '_', 'V', 'L' - }; - - /// - /// Denotes that no checksum is encoded. - /// - private const char NoChecksumMarker = '!'; - - /// - /// A data that is not encrypted. - /// - private IMemoryOwner m_data; - - /// - /// Creates an object that can take (e.g. /T=1234) - /// - /// A data that is not encrypted. - /// - /// This takes the ownership of the data. - /// - public EncodedArgument(IMemoryOwner data) - { - m_data = data; - } - - - public EncodedArgument(string argument) - { - var buffer = MemoryPool.Shared.Rent(Encoding.UTF8.GetByteCount(argument)); - Encoding.UTF8.GetBytes(argument, buffer.Memory.Span); - - m_data = buffer; - } - - public void Dispose() - { - m_data?.Dispose(); - m_data = null!; - } - - /// - /// - /// - /// - /// - /// - /// Thrown when the function could not parse the encoded argument. - /// Message property will carry additional information. - /// - public static EncodedArgument Parse(string argument) - { - // check if argument contains is large enough to contain start marker, checksum and end marker. - if (argument.Length < "//**sqex0003!**//".Length) - { - var exMessage = $"The string ({argument}) is too short to parse the encoded argument." - + $" It should be atleast large enough to store the start marker,checksum and end marker.."; - throw new SqexArgException(exMessage); - } - - if (!argument.StartsWith("//**sqex0003") || !argument[13..].EndsWith("**//")) - { - var exMessage = $"The string ({argument}) doesn't look like the valid argument." - + $" It should start with //**sqex0003 and end with **// string."; - throw new SqexArgException(exMessage); - } - - // Extract the data - var checksum = argument[^5]; - var encryptedData = DecodeUrlSafeBase64(argument.Substring(12, argument.Length - 1 - 12 - 4)); // //**sqex0003, checksum, **// - - // Dedice a partial key from the checksum - var (partialKey, recoverStep) = RecoverKeyFragmentFromChecksum(checksum); - - var decryptedData = MemoryPool.Shared.Rent(encryptedData.Length); - if (!RecoverKey(encryptedData, decryptedData.Memory.Span, partialKey, recoverStep)) - { - // we need to free the memory to avoid a memory leak. - decryptedData.Dispose(); - - var exMessage = $"Could not find a valid key to decrypt the encoded argument."; - throw new SqexArgException(exMessage); - } - - return new EncodedArgument(decryptedData); - } - - private static bool RecoverKey(ReadOnlySpan encryptedData, Span decryptedData, uint partialKey, uint recoverStep) - { - - Span keyBytes = stackalloc byte[8]; - var keyCandicate = partialKey; - - while (true) - { - CreateKey(keyBytes, keyCandicate); - - var blowfish = new Blowfish(keyBytes); - blowfish.Decrypt(encryptedData, decryptedData); - - // Check if the decrypted data looks valid - if (CheckDecryptedData(decryptedData)) - { - return true; - } - - // Try again with the next key. - try - { - keyCandicate = checked(keyCandicate + recoverStep); - } - catch (OverflowException) - { - // We've exhausted the key space and could not find a valid key. - return false; - } - } - } - - /// - /// - /// - /// - /// - private static bool CheckDecryptedData(ReadOnlySpan decryptedData) - { - // TODO - return false; - } - - /// - /// Formats the 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))) - { - var message = $"BUG: Could not create a key"; // This should not fail but.. - throw new InvalidOperationException(message); - } - } - - /// - /// Deduces a partial key from the checksum. - /// - /// - /// `partialKey` can be or'd (a | partialKey) to recover some bits from the key. - /// - /// - /// The partialKey here is very useful because it can further reduce the number of possible key - /// from 0xFFFF to 0xFFF which is 16 times smaller. (and therefore we can initialize the blowfish 16 times less which is quite expensive to do so.) - /// - private static (uint partialKey, uint step) RecoverKeyFragmentFromChecksum(char checksum) - { - return MemoryExtensions.IndexOf(ChecksumTable, checksum) switch - { - -1 => (0x0001_0000, 0x0001_0000), // This covers '!' as well (no checksum are encoded) - var index => ((uint) (index << 16), 0x0010_0000) - }; - } - - /// - /// Converts the url-safe variant of base64 string to bytes. - /// - /// A url-safe variant of base64 string. - private static byte[] DecodeUrlSafeBase64(string payload) - { - var base64Str = payload - .Replace('-', '+') - .Replace('_', '/'); - - return Convert.FromBase64String(base64Str); - } - } -} diff --git a/Dalamud.Bootstrap/Windows/Process.cs b/Dalamud.Bootstrap/Windows/Process.cs index 9972b934e..a18af5e0e 100644 --- a/Dalamud.Bootstrap/Windows/Process.cs +++ b/Dalamud.Bootstrap/Windows/Process.cs @@ -155,6 +155,24 @@ namespace Dalamud.Bootstrap } } + /// + /// Returns a time when the process was started. + /// + public DateTime GetCreationTime() + { + unsafe + { + FileTime creationTime, exitTime, kernelTime, userTime; + + if (Win32.GetProcessTimes(m_handle, &creationTime, &exitTime, &kernelTime, &userTime)) + { + ProcessException.ThrowLastOsError(GetPid()); + } + + return (DateTime)creationTime; + } + } + private string[] ParseCommandLine(ReadOnlySpan commandLine) { unsafe diff --git a/Dalamud.Bootstrap/Windows/Win32.cs b/Dalamud.Bootstrap/Windows/Win32.cs index 6fa8104a9..b986561f0 100644 --- a/Dalamud.Bootstrap/Windows/Win32.cs +++ b/Dalamud.Bootstrap/Windows/Win32.cs @@ -32,16 +32,17 @@ namespace Dalamud.Bootstrap.Windows [DllImport("kernel32", CallingConvention = CallingConvention.Winapi)] public static extern uint GetProcessId(SafeProcessHandle hProcess); + + [DllImport("kernel32", CallingConvention = CallingConvention.Winapi, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetProcessTimes(SafeProcessHandle hProcess, FileTime* lpCreationTime, FileTime* lpExitTime, FileTime* lpKernelTime, FileTime* lpUserTime); } [StructLayout(LayoutKind.Sequential)] - internal partial struct NtStatus + internal struct NtStatus { public uint Value { get; } - } - internal partial struct NtStatus - { public NtStatus(uint value) { Value = value; @@ -70,6 +71,21 @@ namespace Dalamud.Bootstrap.Windows public override string ToString() => $"0x{Value:X8}"; } + [StructLayout(LayoutKind.Sequential)] + internal struct FileTime + { + public uint LowDateTime; + + public uint HighDateTime; + + public static explicit operator DateTime(FileTime value) + { + var time = ((long)value.HighDateTime << 32) | value.LowDateTime; + + return DateTime.FromFileTime(time); + } + } + [StructLayout(LayoutKind.Sequential)] internal struct UNICODE_STRING {