viernes, 24 de diciembre de 2010

Monitorear las ventanas que se abren y cierran en windows mediante un system hook

Aquí se presenta una forma de monitorear las ventanas de Windows a medida que se crean y destruyen, en C#.

Si no te interesa seguir leyendo, aquí puedes bajar el código completo de la aplicación.

Introducción

Dentro de Windows, toda interacción entre ventanas y con el usuario (clicks, movimiento de cursor, etc) se hace a través de mensajes.

Las aplicaciones tienen la capacidad de escuchar y procesar cada uno de los mensajes, por ejemplo, para minimizarse, maximizarse o cerrarse cuando el usuario hace click en el botón correspondiente de la parte superior derecha de la ventana.

En general, una aplicación Windows Forms en .NET, no tiene la necesidad de procesar explícitamente los mensajes, ya que la mayoría de las interacciones se produce automáticamente o por medio de eventos comunes de cada control (Click, Load, etc).

Pero para procesar los mensajes explícitamente, se utiliza una técnica denominada hooking que consiste en hacer que nuestra aplicación implemente un método y lo informe al sistema operativo. Este método será en realidad una función de callback (callback function) y funcionará como un "gancho" (hook) para interceptar los mensajes y poder procesarlos.

Así, cuando Windows procese un evento "enganchable", como el movimiento del cursor o la creación de una ventana, llamará inmediatamente a la función de callback que nuestra aplicación defina.
En el caso de Windows Forms, el método (la función de callback) que intercepta los mensajes, se denomina WndProc y tiene la siguiente firma:
void WndProc(ref System.Windows.Forms.Message m)

Ejemplo

Se presenta una aplicación simple que mostrará en tiempo real una lista con las ventanas, a medida que se van creando o destruyendo.
Para esto, se implementará un hook a nivel shell, para que cada vez que se cree o destruya una ventana, el sistema operativo pase el control a una subrutina de nuestra aplicación y así poder informar al usuario del evento.

A continuación se muestra el código necesario para registrar nuestra aplicación como gancho (o hook) de los mensajes del shell de windows en un Window Form en C#.
uMsgNotify = Win32API.RegisterWindowMessage("SHELLHOOK");
Win32API.RegisterShellHookWindow(this.Handle);  

La llamada a RegisterWindowMessage se hace para obtener un identificador único de los mensajes. Este identificador lo necesitaremos dentro de la función de callback para identificar a los mensajes que nos interesa escuchar.
La llamada a RegisterShellHookWindow se hace para registrar nuestro WinForm como gancho de los eventos del shell de windows.

Una vez hecho esto, procedemos a sobreescribir la función de callback en nuestro WinForm, para interceptar los mensajes de WindowCreated y WindowDestroyed, como se muestra a continuación:
protected override void WndProc(ref System.Windows.Forms.Message m)
{
    IntPtr hWnd; 
    if (m.Msg == uMsgNotify)
    {
        switch (m.WParam.ToInt32())
        {
            case (int)Win32API.ShellEvents.HSHELL_WINDOWCREATED:
                hWnd = m.LParam;
                // La ventana cuyo handler es hWnd, fue creada
                ...
                break;
            case (int)Win32API.ShellEvents.HSHELL_WINDOWDESTROYED:
                hWnd = m.LParam;
                // La ventana cuyo handler es hWnd, fue cerrada
                ...
                break;
        }
    }
    base.WndProc(ref m);
}

Lo único que resta por hacer, es obtener el nombre de la ventana que fue creada/destruida, así como el nombre del proceso (ejecutable) al que pertenece, todo a partir del identificador de la ventana (hWnd).

Para obtener el nombre de la ventana a partir de su identificador, utilizamos la función GetWindowTextLength y GetWindowText como se muestra a continuación:
private string GetWindowName(IntPtr hwnd)
{
    StringBuilder sb = new StringBuilder();
    int longi = Win32API.GetWindowTextLength(hwnd) + 1;
    sb.Capacity = longi;
    Win32API.GetWindowText(hwnd, sb, sb.Capacity);
    return sb.ToString();
}

Obtener el nombre del ejecutable al que pertenece la ventana es un poco más tedioso, ya que se necesitan 4 llamadas a la API:

private string ExePathFromHwnd(IntPtr hWnd)
{
    StringBuilder sb = new StringBuilder(Win32API.MAX_PATH);
    int pid = 0;
    Win32API.GetWindowThreadProcessId(hWnd, ref pid);
    IntPtr hProc = Win32API.OpenProcess(Win32API.ProcessAccess.AllAccess, false, pid);
    if (hProc != IntPtr.Zero)
    {
        uint lRet = Win32API.GetModuleFileNameEx(hProc, IntPtr.Zero, sb, Win32API.MAX_PATH);
        Win32API.CloseHandle(hProc);
    }
    return sb.ToString();
}

Juntando todo esto en una aplicación, podemos generar algo así:


Aquí puedes bajar el código completo de la aplicación.

Feliz navidad !

1 comentario:

Marco F Garcia dijo...

Hola Federico,

Este artículo se me hizo interesante porque hace unos dias estuve probando un software que supongo funciona bajo una esquema similar. Si comparto el enlace, me podrias dar el norte sobre su potencial para usos de seguridad.

Gracias y con gusto lo publico al recibir tu permiso.