Come Fare un motore antivirus
Introduzione
Quando è in roaming in tutto il techies forum, mi capita spesso di vedere alcune persone (e molte non sono molto esperto), chiedendo “Come faccio a fare un antivirus”, a volte con non molto adatto lingue (bat, PHP, …) e di avere una idea sbagliata di ciò che un antivirus è, e come dovrebbe essere realizzato.
Ho anche visto molti “software antivirus” realizzati da bambini, con pochissime persone ancora a scuola e circa 4 ore al giorno di codifica per diverse settimane. Non sto dicendo che i bambini non sono esperti, ma sto dicendo che costruire un motore antivirus ha bisogno di molte persone esperte con un lavoro a tempo pieno più molto tempo per rilasciare un software decente o molti soldi per pagarli 🙂 (nel caso in cui non siano volontari).
Quindi, tratterò qui le linee guida per una codifica antivirus di base, per Windows e in C/C++. Si possono trovare qui i puntatori per progettare un motore antivirus, o semplicemente imparare come la maggior parte di loro sono costruiti.
Protezione
Per una buona protezione, un antivirus deve avere almeno un driver, per essere in grado di eseguire il codice nel kernel e nel complesso avere accesso alle API del kernel. A partire da Vista, Microsoft ha capito che l’industria Antivirus aveva bisogno di chiavi per entrare nel kernel e attivare filtri in luoghi strategici, come file system, registro e rete. Non essere stordito se la costruzione di un antivirus per i sistemi pre-Vista può essere un vero dolore, perché non è stato progettato per questo.
- Tuttavia, sui sistemi Pre-Vista, le aziende antivirus utilizzavano funzionalità simili a rootkit per proteggere le porte (anche se non era affatto raccomandato da Microsoft) ed essere in grado di proteggere il sistema. Hanno usato quelli che chiamiamo “Ganci” (deviazioni API per scopi di filtraggio).
- Su Vista+, Microsoft ha fornito API per inserire il nostro driver di basso livello tra le chiamate userland e le API del kernel. In questo modo, è facile registrare un prodotto antivirus nel kernel. Inoltre, questo tipo di sistema basato sulla registrazione ci consente di inviare la nostra sicurezza del sistema in livelli, in cui diversi prodotti con obiettivi diversi possono convivere. Questo non era il caso di hooks, poiché l’implementazione era totalmente dipendente dal prodotto.
NOTA: Non coprirò le soluzioni alternative con ganci per i sistemi pre-Vista, perché è facile da trovare su Internet, e perché avrebbe bisogno di un intero capitolo per spiegare come agganciare, dove agganciare e così so Ma devi sapere che è la stessa idea delle API del kernel, tranne che devi implementare te stesso ciò che Microsoft ha fornito sui sistemi Vista+.
Per conoscere i driver di codifica, è possibile verificare che i link utili:
http://msdn.microsoft.com/en-us/library/windows/hardware/gg490655.aspx
http://www.codeproject.com/Articles/9504/Driver-Development-Part-1-Introduction-to-Drivers
Per conoscere gli hook, puoi controllare quell’esempio di base:
http://www.unknowncheats.me/forum/c-and-c/59147-writing-drivers-perform-kernel-level-ssdt-hooking.html
Process
La prima cosa da cui proteggere l’utente è l’avvio di processi dannosi. Questa è la cosa fondamentale. Antivirus dovrebbe registrare un callback PsSetCreateProcessNotifyRoutineEx. In questo modo, su ogni creazione del processo e prima che il thread principale inizi a funzionare (e causi cose dannose) il callback antivirus viene notificato e riceve tutte le informazioni necessarie.
Riceve il nome del processo, l’oggetto file, il PID e così via. Poiché il processo è in sospeso, il driver può dire al suo servizio di analizzare la memoria del processo per qualsiasi cosa dannosa. Trova qualcosa, il driver imposterà semplicemente CreationStatus su FALSE e restituirà.
NTSTATUS PsSetCreateProcessNotifyRoutineEx( _In_ PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, _In_ BOOLEAN Remove);
VOID CreateProcessNotifyEx( _Inout_ PEPROCESS Process, _In_ HANDLE ProcessId, _In_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo);
typedef struct _PS_CREATE_NOTIFY_INFO { SIZE_T Size; union { ULONG Flags; struct { ULONG FileOpenNameAvailable :1; ULONG Reserved :31; }; }; HANDLE ParentProcessId; CLIENT_ID CreatingThreadId; struct _FILE_OBJECT *FileObject; PCUNICODE_STRING ImageFileName; PCUNICODE_STRING CommandLine; NTSTATUS CreationStatus;} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
Thread
Nella stessa idea che per i processi, i thread possono essere un modo per le cose dannose di causare danni. Ad esempio, si può iniettare del codice in un processo legittimo e avviare un thread remoto su quel codice all’interno del contesto del processo (facile da seguire? 🙂 ). In questo modo, un processo legittimo può fare cose dannose.
Possiamo filtrare nuovi thread con il callback PsSetCreateThreadNotifyRoutine. Ogni volta che viene creato un thread, l’antivirus viene notificato con il TID e il PID. Pertanto, è in grado di esaminare il codice dell’indirizzo iniziale del thread, analizzarlo e interrompere il thread o riprenderlo.
NTSTATUS PsSetCreateThreadNotifyRoutine( _In_ PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine);
VOID(*PCREATE_THREAD_NOTIFY_ROUTINE) ( IN HANDLE ProcessId, IN HANDLE ThreadId, IN BOOLEAN Create );
Immagini
La terza minaccia dinamica riguarda le immagini che possono essere caricate in memoria. Un’immagine è un file PE, un file EXE, una DLL o un file SYS. Per ricevere una notifica di immagini caricate, è sufficiente registrare PsSetLoadImageNotifyRoutine. Tale callback ci consente di essere avvisati quando l’immagine viene caricata nella memoria virtuale, anche se non viene mai eseguita. Possiamo quindi rilevare quando un processo tenta di caricare una DLL, di caricare un driver o di attivare un nuovo processo.
Il callback ottiene informazioni sul percorso completo dell’immagine (utile per l’analisi statica), e il più importante a mio parere, l’indirizzo di base dell’immagine (per l’analisi in memoria). Se l’immagine è dannosa, l’antivirus può usare piccoli trucchi per evitare l’esecuzione, come analizzare l’immagine in memoria e andare al punto di ingresso, quindi chiamare l’opcode dell’assembly “ret” per annullarlo.
NTSTATUS PsSetLoadImageNotifyRoutine( _In_ PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine);
VOID (*PLOAD_IMAGE_NOTIFY_ROUTINE)( __in_opt PUNICODE_STRING FullImageName, __in HANDLE ProcessId, __in PIMAGE_INFO ImageInfo );
typedef struct _IMAGE_INFO { union { ULONG Properties; struct { ULONG ImageAddressingMode : 8; //code addressing mode ULONG SystemModeImage : 1; //system mode image ULONG ImageMappedToAllPids : 1; //mapped in all processes ULONG Reserved : 22; }; }; PVOID ImageBase; ULONG ImageSelector; ULONG ImageSize; ULONG ImageSectionNumber;} IMAGE_INFO, *PIMAGE_INFO;
Filesystem
Una volta che ogni cosa dinamica è protetta, un antivirus dovrebbe essere in grado di notificare all’utente le cose dannose al volo, non solo quando stanno per iniziare. Un antivirus dovrebbe essere in grado di eseguire la scansione dei file quando l’utente apre una cartella, un archivio o quando viene scaricato sul disco. Di più, un antivirus dovrebbe essere in grado di proteggere se stesso, vietando qualsiasi programma per eliminare i suoi file.
Il modo per fare tutto questo, è quello di installare un driver nel file system, e più specificamente un minifilter di un filtro legacy (vecchio modo). Qui parleremo di minifilter.
Un minifilter è un tipo specifico di driver, in grado di registrare callback su ogni operazione di lettura/scrittura effettuata sul file system (funzioni principali IRP). Un IRP (Interrupt Request Paquet) è un oggetto utilizzato per descrivere un’operazione di lettura/scrittura sul disco, che viene trasmessa insieme allo stack del driver. Il minifilter verrà semplicemente inserito in quello stack e riceverà quell’IRP per decidere cosa farne (allow/deny operation).
Per un piccolo esempio di minifilter, controlla quel link utile o quello. Le linee guida di Microsoft sono qui.
Troverete anche 2 esempi della documentazione WDK qui e qui.
Un callback minifilter di base ha questo aspetto. Ci sono 2 tipi di callback, Pre operazione e Post operazione, che sono in grado di filtrare prima di dopo la query. Ecco uno pseudo codice di preoperazione:
FLT_PREOP_CALLBACK_STATUS PreOperationCallback (__inout PFLT_CALLBACK_DATA Data, __in PCFLT_RELATED_OBJECTS FltObjects, __deref_out_opt PVOID *CompletionContext){ ... if ( all_good ) { return FLT_PREOP_SUCCESS_NO_CALLBACK; } else { // Access denied Data->IoStatus.Information = 0; Data->IoStatus.Status = STATUS_ACCESS_DENIED; return FLT_PREOP_COMPLETE; } }
Registry
Il registro è uno dei luoghi più critici da proteggere. Ci sono molti molti modi per un malware per mantenere mano persistente sul sistema registrando un singolo (o pochi) chiavi / valori nel registro di sistema. I luoghi più noti sono le chiavi di esecuzione e i servizi. Questo è anche il luogo in cui l’antivirus può essere sconfitto (insieme al file system), semplicemente rimuovendo le chiavi del driver/servizio, in modo che non si riavvii più all’avvio del sistema.
Questa non è una vera necessità per un antivirus per proteggere i luoghi di riavvio, la maggior parte di loro no. Ma devono proteggere le loro chiavi di registro di installazione, per evitare di essere sconfitti facilmente dai malware. Questo può essere fatto registrando CmRegisterCallback.
Il callback fornisce informazioni sufficienti per ottenere il nome completo della chiave, il tipo di accesso (Creare, rinominare, Eliminare,…) e il PID del chiamante. In questo modo è facile concedere l’accesso o meno alla chiamata, impostando il campo di stato della callback post operazione.
NTSTATUS CmRegisterCallbackEx(_In_ PEX_CALLBACK_FUNCTION Function,_In_ PCUNICODE_STRING Altitude,_In_ PVOID Driver,_In_opt_ PVOID Context,_Out_ PLARGE_INTEGER Cookie,_Reserved_ PVOID Reserved);
EX_CALLBACK_FUNCTION RegistryCallback;NTSTATUS RegistryCallback( _In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2){ ... }Argument1 = typedef enum _REG_NOTIFY_CLASS { RegNtDeleteKey, RegNtPreDeleteKey = RegNtDeleteKey,...Argument2 = typedef struct _REG_POST_OPERATION_INFORMATION { PVOID Object; NTSTATUS Status; PVOID PreInformation; NTSTATUS ReturnStatus; PVOID CallContext; PVOID ObjectContext; PVOID Reserved;} REG_POST_OPERATION_INFORMATION, *PREG_POST_OPERATION_INFORMATION;
di Rete (Firewall)
Per la guardia alle porte di tutto il traffico internet che può essere enorme su alcuni sistemi (server, la larghezza di banda enorme di utenti) senza essere rallentati dalla commutazione di contesto che avviene a livello utente, è totalmente sconsigliato di installare un firewall che non hanno driver sottostante, fatta eccezione per alcuni browser web filtri che possono essere sufficienti per il traffico http, ma che non protegge da malware comunicazione in/out.
Per avere una corretta implementazione del firewall, si dovrebbe codificare un NDIS, TDI o un altro metodo per il driver di filtraggio IP di basso livello. NDIS / TDI è un po ‘ complicato da fare e richiederebbe molta conoscenza (più di altri filtri a mio parere).
Ad ogni modo, ecco alcuni suggerimenti per iniziare a codificare un driver di questo tipo, le linee guida Microsoft e il vecchio tutorial codeproject (ma ancora buono da leggere), un esempio di firewall NDIS e un esempio di firewall TDI. Ecco anche una buona scrittura sul trucco di bypass del firewall NDIS e una piccola spiegazione sullo stack dei driver di rete,
Protezione Userland
La protezione userland non è una necessità, ma può essere un modulo aggiuntivo contro i banchieri Trojan e più specificamente contro le spie di processo. Sono generalmente iniettati in ogni processo, per diversi motivi.
In primo luogo, sono in grado (su richiesta) di uccidere il processo se è stato identificato come malware (questo non dovrebbe accadere, perché gli AV dovrebbero fermarlo prima che inizi). È sempre più facile interrompere un processo quando si è nel suo contesto.
In secondo luogo sono in grado di proteggere i processi critici, come i browser Web, contro l’aggancio di malware in grado di deviare e filtrare le chiamate API per raccogliere password, informazioni bancarie e reindirizzare il flusso Internet ai server malware. Guardano solo per la modifica IAT, per lo splicing e possono anche impostare i ganci stessi per evitare LoadLibray di una DLL di malware e quindi vietare determinati metodi di iniezione di codice.
Un modo semplice per iniettare una DLL protector in tutti i processi è quello di utilizzare la chiave di registro AppInitDll per registrare la DLL protector. Caricherà la DLL in ogni processo avviato sul sistema, non appena collegheranno l’Utente32.immagine dll (la maggior parte di loro lo fa).
Motore di analisi
Il motore di analisi è una delle parti più importanti, è responsabile dell’analisi di campioni di file/memoria provenienti dai driver. Se deve essere veloce (anche con un enorme database), e dovrebbe essere in grado di gestire la maggior parte dei tipi di file (Self-estratto i file eseguibili, Archivi RAR, ZIP, pranzo al file UPX, … ) e quindi dovrebbe avere molti moduli per fare questo:
- Unpacker : il modulo deve essere in grado di rilevare e decomprimere il packers (UPX, Armadillo, ASPack, …)
- Firma di motore: Il database dell’antivirus contiene milioni di firme, e il motore dovrebbe essere in grado di veloce ricerca in un campione. Quindi, un algoritmo molto potente dovrebbe farne parte. Alcuni esempi : AhoCorasick, RabinKarp, algoritmi di corrispondenza delle stringhe.
- Sandbox : quel modulo non è necessario, ma sarebbe un vantaggio per poter eseguire campioni in una memoria limitata, senza alcun effetto sul sistema. Ciò potrebbe aiutare a decomprimere i campioni imballati con packer sconosciuti e aiutare il motore euristico (vedere dopo) a rilevare le chiamate API che potrebbero essere considerate sospette o dannose. Qualche buona sandbox qui.
- Motore euristico: come detto sopra, il motore euristico non cerca le firme, ma piuttosto cerca comportamenti sospetti(es. esempio che apre una connessione sul sito web hxxp: / / malware_besite. com). Ciò può essere fatto mediante analisi statica o tramite una sandbox.
Sintassi della firma
La sintassi della firma è il “dizionario” della lingua che il motore di firma comprende. È un modo per formalizzare qual è il modello da trovare, come cercarlo e dove cercarlo nel campione. La sintassi deve essere abbastanza semplice da comprendere per i ricercatori, abbastanza potente da gestire ogni caso d’uso e facile da analizzare per migliori prestazioni del motore.
VirusTotal ha sviluppato una buona sintassi e motore (progetto Yara), che è open source. Questo dovrebbe essere un buon puntatore per creare la tua sintassi o semplicemente usarla. Ecco anche un buon post blog su come creare firme per antivirus.
Esempio di firma:
rule silent_banker : banker{ meta: description = "This is just an example" thread_level = 3 in_the_wild = true strings: $a = {6A 40 68 00 30 00 00 6A 14 8D 91} $b = {8D 4D B0 2B C1 83 C0 27 99 6A 4E 59 F7 F9} $c = "UVODFRYSIHLNWPEJXQZAKCBGMT" condition: $a or $b or $c}
Autoprotezione
L’autoprotezione è molto importante per un antivirus, per evitare di essere sconfitto da un malware e continuare a proteggere l’utente. Questo è il motivo per cui un antivirus dovrebbe essere in grado di proteggere la propria installazione e mantenere la persistenza al riavvio.
Ci sono diversi luoghi da proteggere: file, chiavi di registro, processi/thread, Memoria.
- La protezione dei file è implementata nel minifilter, con regole particolari sui file dell’antivirus (Nessun accesso in cancellazione, ridenominazione, spostamento, scrittura).
- La protezione del registro viene inserita nel filtro del registro, con accesso negato per le chiavi di registro del driver e del servizio.
- Il driver thread sono protetti, perché è abbastanza impossibile scaricare il modulo del kernel senza inchiodarsi
- Per essere in grado di proteggere il servizio, che è un ambiente di processo, 2 soluzioni:
- Il modo più semplice sarebbe quello di aggiungere delle regole per i guasti in il gestore del servizio, e impostare ogni fallimento regola “riavvia il servizio”. In questo modo, quando il servizio non viene fermato da Service manager, si riavvia. Naturalmente, il servizio non dovrebbe essere in grado di accettare comandi fino a quando il sistema non si riavvia (o si arresta).
- Il secondo metodo, che è più generico, sarebbe quello di impostare i callback sugli handle di processo con ObRegisterCallbacks.
impostando il Tipo di PsProcessType e l’Operazione di OB_OPERATION_HANDLE_CREATE, riceverai un Pre e Post operazione richiamata, e si sono in grado di tornare ACCESS_DENIED in ReturnStatus se l’handle del processo interrogato ha GrantedAccess che hanno processo di terminare i diritti (o processo i diritti di scrittura, o qualsiasi cosa che può causare un crash/kill), e, naturalmente, se il processo è un antivirus deve guard (servizio fornito, per esempio).
Naturalmente, è anche necessario proteggere l’handle duplicato e il PsThreadType per evitare qualsiasi metodo di terminazione che richiede di afferrare una maniglia sul processo o un thread. Ecco un piccolo esempio di utilizzo di tale callback.
NTSTATUS ObRegisterCallbacks( _In_ POB_CALLBACK_REGISTRATION CallBackRegistration, _Out_ PVOID *RegistrationHandle);
typedef struct _OB_CALLBACK_REGISTRATION { USHORT Version; USHORT OperationRegistrationCount; UNICODE_STRING Altitude; PVOID RegistrationContext; OB_OPERATION_REGISTRATION *OperationRegistration;} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;
typedef struct _OB_OPERATION_REGISTRATION { POBJECT_TYPE *ObjectType; OB_OPERATION Operations; POB_PRE_OPERATION_CALLBACK PreOperation; POB_POST_OPERATION_CALLBACK PostOperation;} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;
VOID ObjectPostCallback( _In_ PVOID RegistrationContext, _In_ POB_POST_OPERATION_INFORMATION OperationInformation);
typedef struct _OB_POST_OPERATION_INFORMATION { OB_OPERATION Operation; union { ULONG Flags; struct { ULONG KernelHandle :1; ULONG Reserved :31; }; }; PVOID Object; POBJECT_TYPE ObjectType; PVOID CallContext; NTSTATUS ReturnStatus; POB_POST_OPERATION_PARAMETERS Parameters;} OB_POST_OPERATION_INFORMATION, *POB_POST_OPERATION_INFORMATION;
typedef union _OB_POST_OPERATION_PARAMETERS { OB_POST_CREATE_HANDLE_INFORMATION CreateHandleInformation; OB_POST_DUPLICATE_HANDLE_INFORMATION DuplicateHandleInformation;} OB_POST_OPERATION_PARAMETERS, *POB_POST_OPERATION_PARAMETERS;
typedef struct _OB_POST_CREATE_HANDLE_INFORMATION { ACCESS_MASK GrantedAccess;} OB_POST_CREATE_HANDLE_INFORMATION, *POB_POST_CREATE_HANDLE_INFORMATION;
GUI (Graphical User Interface)
Questa è la parte visibile dell’iceberg. A mio parere, una (forse LA) parte più importante se vuoi vendere il tuo prodotto. Gli utenti amano ciò che è bello, facile da usare, intuitivo. Anche se non è efficiente al 100%. La GUI deve essere sexy.
La GUI è solo una shell vuota, esegue solo trattamenti grafici e invia / riceve comandi al core (il servizio). Visualizza anche le barre di avanzamento, ciò che viene analizzato, fornisce la configurazione e quindi Here Ecco l’interfaccia utente Avast. Sexy, vero? 🙂
Architettura
L’architettura globale potrebbe essere qualcosa che assomiglia a questo:
- GUI: Nessun diritto amministrativo, DEBOLE
- DLL di protezione: protezione del browser web, MEDIO
- Servizio: diritti di amministratore. Serve come gateway per il codice del kernel e prendere decisioni insieme ad alcuni database, STRONG
- Driver(s) : Filtri del kernel, STRONG
La GUI non ha bisogno di alcun diritto amministrativo, richiede solo azioni dell’utente e le trasmette al servizio. Visualizza anche lo stato del prodotto. Niente di più, questo non è il suo obiettivo. Se la GUI viene uccisa, questo non è un problema in quanto il servizio dovrebbe essere in grado di riavviarlo.
Le DLL di guardia (se presenti), vengono iniettate in modo massiccio in tutti i processi e dovrebbero essere in grado di cercare ganci IAT e/o thread dannosi. Dovrebbero essere abbastanza difficili da scaricare o sconfiggere. Non sono critici ma importanti.
Il servizio è il cuore del prodotto. Dovrebbe essere unkillable, o almeno dovrebbe essere in grado di auto-riavviare su kill. Il servizio è responsabile della comunicazione tra tutti i moduli del prodotto, invia comandi ai driver, prende comandi dall’utente e interroga il database per l’analisi del campione. Questo è il cervello.
Anche i driver del kernel sono fondamentali. Sono i tentacoli che raccolgono informazioni su tutto ciò che accade sul sistema e li trasmettono al servizio per la decisione. Sono anche in grado di negare l’accesso a luoghi sorvegliati, in base alla decisione del servizio.
Conclusione
Fare un forte, stabile e affidabile, il motore antivirus è un compito complicato, che ha bisogno sperimentato persone con una forte conoscenza nel kernel di windows, programmazione, programmazione di applicazioni di windows, l’interfaccia grafica design, architettura del software, l’analisi del malware, …
Costruzione di driver stabili è anche un compito complicato, a causa di un piccolo granello di sabbia può mandare in crash l’intero sistema. Ha bisogno di test, test e molti test. Quando il tuo motore è finito e funzionante, dovrai solo assumere ricercatori per analizzare malware e aggiungere firme al tuo database What Cosa stai aspettando? Avanti! Links
Collegamenti
- http://www.symantec.com/connect/articles/building-anti-virus-engine