PDA

Просмотр полной версии : [Статья] KeyLogger [2]


HideFebruary
05.03.2016, 17:24
Предыдущая статья: KeyLogger [1] ([Ссылки могут видеть только зарегистрированные и активированные пользователи])

Немного потестировав код из прошлой статьи, понял, что механизм отстойный. Сегодня мы с тобой, мой любимый читатель, модифицируем логику улучшив наш кейлоггер

Первое на что хочу обратить внимание из прошлой реализации это количество флуда от события. При чем я на это начал обращать внимание когда увеличил скорость обработки в методе IsPressing, заменив сравнение с константой, на конвертацию в булево значение. После такого буста ( по понятным причинам ) со старой реализацией, при типичном нажатии на клавишу выскакивало до десяти уведомлений! В тот момент я понял, что не хочу видеть флуда как такого и не хочу видеть неоднократное уведомление в принципе, если клавишу просто зажали.

Для удаления флуда нам нужно запоминать при каждой проверке клавиши её значение и если в прошлый раз она была нажата и сейчас нажата тоже, то её все еще держут и уведомлять не нужно. При таком подходе всплывает еще одно требование, нужно знать когда клавишу отпустили, что бы при анализе логов можно было понять какие буквы писались под нажатым шифтом. Для этого нам будет нужен словарь:


private readonly Dictionary<Keys, bool> _cacheFlag;


В методе OnTimer изменим логику. Сначала мы узнаем нажата ли клавиша и достанем прошлый флаг. Если в прошлый раз она была нажата и сейчас тоже, то мы пропускаем этот момент и ничего не делаем. Если сейчас клавиша нажата, а в прошлый раз была не нажата, то собственно, уведомляем о нажатии. Если же сейчас клавиша не нажата, а в прошлый раз была нажата, уведомляем о том, что клавишу отпустили. Все достаточно логично.


private void OnTimer ( Object sender, System.Timers.ElapsedEventArgs e )
{
foreach( var key in _checkingKeys )
{
var pressing = IsPressing( key );
var flag = _cacheFlag[key];

if ( pressing && !flag ) OnAction ( key, KeyAction.Pressed );
if ( !pressing && flag ) OnAction ( key, KeyAction.UnPressed );

_cacheFlag [ key ] = pressing;
}
}


Как можно заметить, появился новый метод OnAction, который принимает в себя значение клавиши и флаг того, что за действие было произведено, в этом методе уже происходит непосредственное уведомление подписчиков.


private void OnAction( Keys key, KeyAction action )
{
var buffer = ActionHappened;
buffer?.Invoke ( this, new KeyActionEventArgs ( key, action ) );
}


В этом методе можно заметить еще одни изменения, это добавление нового класса KeyActionEventArgs который служит нашей собственной реализацией параметров уведомления, добавление перечисления KeyAction и изменение самого события. KeyAction состоит всего из двух флагов.


public enum KeyAction
{
Pressed,
UnPressed,
}


А вот как я придумал реализовать класс параметров события:


public class KeyActionEventArgs : EventArgs
{
public KeyActionEventArgs( Keys key, KeyAction action )
{
TimeAction = DateTime.UtcNow;

Key = key;
KeyAction = action;
}

public readonly DateTime TimeAction;
public readonly Keys Key;
public KeyAction KeyAction;
}


В классе мы знаем, что за кнопка пришла, какое действие и точное время по UTC когда произошло событие, время нужно для анализа логов.

Реализация самого события тоже изменилась, теперь у него нет оберточного свойства которое инкапсулирует наш умный таймер, как показали тесты это лишняя логика и она никак не влияет на нагрузку процессора. Событие теперь выглядит так:


public event EventHandler<KeyActionEventArgs> ActionHappened;


С таймером мы все же кое-что сделаем. Так как нам не нужно следить больше за его запуском, он постоянно будет работать. Учитывая, что инстансов нашего класса может быть много, соответственно таймеров тоже будет много и это не хорошо как минимум с точки зрения концепции. Я предлагаю сделать один таймер общий для всех инстансов, пометив его статичным.


private readonly static System.Timers.Timer _timer;


А инициализировать и запускать его будем в статичном конструкторе. Для тех кто не знает, статичный конструктор вызывается один раз при первом обращении к классу и служит как раз для относительно безопасной инициализации статических переменных.


static KeyWatcher ()
{
const double interval = 10;
_timer = new System.Timers.Timer ( interval );
_timer.Start ();
}


Теперь осталось изменить обычный конструктор нашего класса. В нем мы добавили инициализацию флагов клавиш. А также изменим аргумент приходящий в конструктор, для более удобного использования. Кто не знает, params перед типом аргумента означает, что мы можем передавать массив значений без явного определения массива, просто через запятую и это очень здорово.


public KeyWatcher( params Keys[] keys )
{
_checkingKeys = keys.Distinct ().ToArray ();
_cacheFlag = new Dictionary<Keys, bool> ();

foreach( var key in _checkingKeys )
{
_cacheFlag [ key ] = false;
}

_timer.Elapsed += OnTimer;
}


Решил добавить еще и стандартный конструктор (который без параметров), также для более удобного использования когда нам нужно смотреть за всеми клавишами.


public KeyWatcher () : this ( Enum.GetValues ( typeof ( Keys ) ).Cast<Keys> ().ToArray () )
{
}


Кстати, метод IsPressing теперь выглядит вот так:

public static bool IsPressing ( Keys key )
=> Convert.ToBoolean ( GetAsyncKeyState ( key ) );


Это намного быстрее работает и константа теперь не требуется.

Класс готов к использованию. Проверим его таким кодом в мейне:


class Program
{
static void Main ( string [ ] args )
{
var keyWatcher = new KeyWatcher();
keyWatcher.ActionHappened += KeyWatcherActionHappened;
ReadLine ();

keyWatcher.Close ();
}

private static void KeyWatcherActionHappened ( Object sender, KeyActionEventArgs e )
{
if( e.KeyAction == KeyAction.UnPressed )
{
if ( e.Key != Keys.ShiftKey ) return;
WriteLine ( $"{e.Key.ToString ()} is UnPressed" );
}

WriteLine ( $"{e.Key.ToString()} is pressed" );
}
}


Тут мы выводим все кнопки которые нажались и информацию о том когда отпустили шифт.

Исходник под спойлером.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using static System.Console;

namespace ConsoleApp
{
class Program
{
static void Main ( string [ ] args )
{
var keyWatcher = new KeyWatcher();
keyWatcher.ActionHappened += KeyWatcherActionHappened;
ReadLine ();

keyWatcher.Close ();
}

private static void KeyWatcherActionHappened ( Object sender, KeyActionEventArgs e )
{
if( e.KeyAction == KeyAction.UnPressed )
{
if ( e.Key != Keys.ShiftKey ) return;
WriteLine ( $"{e.Key.ToString ()} is UnPressed" );
}

WriteLine ( $"{e.Key.ToString()} is pressed" );
}
}

public sealed class KeyWatcher
{
static KeyWatcher ()
{
const double interval = 10;
_timer = new System.Timers.Timer ( interval );
}

public KeyWatcher( params Keys[] keys )
{
_checkingKeys = keys.Distinct ().ToArray ();
_cacheFlag = new Dictionary<Keys, bool> ();

foreach( var key in _checkingKeys )
{
_cacheFlag [ key ] = false;
}

_timer.Elapsed += OnTimer;
_timer.Start ();
}

public KeyWatcher () : this ( Enum.GetValues ( typeof ( Keys ) ).Cast<Keys> ().ToArray () )
{

}

public event EventHandler<KeyActionEventArgs> ActionHappened;

public static bool IsPressing ( Keys key )
=> Convert.ToBoolean ( GetAsyncKeyState ( key ) );

private void OnTimer ( Object sender, System.Timers.ElapsedEventArgs e )
{
foreach( var key in _checkingKeys )
{
var pressing = IsPressing( key );
var flag = _cacheFlag[key];

if ( pressing && !flag ) OnAction ( key, KeyAction.Pressed );
if ( !pressing && flag ) OnAction ( key, KeyAction.UnPressed );

_cacheFlag [ key ] = pressing;
}
}

private void OnAction( Keys key, KeyAction action )
{
var buffer = ActionHappened;
buffer?.Invoke ( this, new KeyActionEventArgs ( key, action ) );
}

private readonly static System.Timers.Timer _timer;

private readonly Keys[] _checkingKeys;
private readonly Dictionary<Keys, bool> _cacheFlag;

[DllImport ( "User32.dll" )]
private static extern short GetAsyncKeyState ( Keys key );

public void Close () => _timer.Close ();
}

public class KeyActionEventArgs : EventArgs
{
public KeyActionEventArgs( Keys key, KeyAction action )
{
TimeAction = DateTime.UtcNow;

Key = key;
KeyAction = action;
}

public readonly DateTime TimeAction;
public readonly Keys Key;
public KeyAction KeyAction;
}

public enum KeyAction
{
Pressed,
UnPressed,
}
}