Dopo aver visto la ricerca dei files infetti da virus usando il linguaggio SVDL (ricerca algoritmica), oggi vedremo l’analogo per la memoria: MVDL (Memory Virus Detect Language).
Come nel caso dei files, un virus che deve prendere il controllo del sistema per rimanervi attivo in memoria necessita di avere un sistema che gli permetta di evitare di installare più e più volte se stesso, pena un abbattimento delle performance del pc ad ogni operazione eseguita.
Per comprendere come questo meccanismo viene implementato, dobbiamo però conoscere come funziona un microprocessore e come il bios e il sistema operativo lo utilizzino per offrire le chiamate al sistema operativo. Ovviamente quello che vediamo è solo a livello molto semplice, giusto per avere una infarinatura, ed in questo siamo anche agevolati dal fatto che stiamo analizzando virus degli anni 90 che funzionavano su un sistema operativo molto semplice.
Il microprocessore gestisce una sequenza di istruzioni il cui flusso è determinato dal programmatore con un programma che ha un “ciclo” di operazioni prestabilito. Per far si che possano essere gestiti degli eventi esterni in real-time, il processore può essere notificato tramite un segnale esterno (l’interrupt) che deve sospendere le attuali operazioni e dedicarsi a risolvere la chiamata esterna perchè urgente.
Ad esempio, se il microprocessore controlla una fresa di una macchina a controllo numerico e l’operatore preme il pulsante di emergenza per bloccarla, il microprocessore viene notificato dell’evento non previsto e viene attivata una routine che blocca tutte le operazioni, in attesa che venga ripristinato il tutto (questo esempio è preso in prestito dai microcontrollori, per i processori normali il segnale può essere la notifica di un dispositivo collegato al pc, come un processore grafico, l’hardisk, la scheda seriale, ecc.).
Questo tipo di eventi a cui si richiede una risposta immediata sono anche chiamati “Non mascherabili” (NMI) perchè devono sempre bloccare il processore. Altri tipi di eventi, normalmente chiamati semplicemente Interrupt (IRQ), possono essere disabilitati o attivati a richiesta del programma.
Quando il processore viene interrotto, esso salva il suo stato attuale e salta ad eseguire un programma in una posizione dipendente dal tipo di processore e dal tipo di interrupt (possono essere in locazioni fisse o dipendenti da una tabella compilata). La routine chiamata si occupa di gestire la fonte dell’interrupt e prima di uscire notifica che l’interrut è stato gestito alla periferica che lo ha emesso (in modo che non arrivi nuovamente la stessa richiesta di interrupt nel momento in cui si ritorna ad eseguire il flusso originario del programma che era stato interrotto)
Questo meccanismo, oltre che avvenire per segnali hardware, può essere invocato anche tramite istruzioni software (nei processori x86 sono chiamate INT) e possono accettare come parametro (sempre nei processori x86) un numero che specifica quale locazione attivare tra 255 disponibili.
L’istruzione è utile quindi per invocare le funzioni di sistema operativo o del bios, dato che il programma viene interrotto nel punto in cui compare l’istruzione di interrupt e il controllo passa alla relativa funzione gestita dal sistema operativo che ne esplica la funzione richiesta.
Un virus che pertanto vuole assumere il controllo del sistema operativo può sostituire la chiamata di un interrupt (magari quelle che gestiscono l’accesso ai files) per farla puntare a se stesso in memoria e bypassare il normale funzionamento del sistema operativo a suo comando.
Se una persona apre un file dovrebbe essere invocato il servizio del sistema operativo dedicato alla lettura dei files, ma invece si attiva la parte del virus che infetta il file che si vuole aprire e poi ne passa il controllo al sistema operativo come se nulla fosse (e se è di tipo stealth, magari fa vedere al sistema operativo il file non infetto…).
Torniamo ora alla possibilità di verificare se un virus è attivo in memoria o meno.
Quando un programma infetto viene eseguito, una parte del codice del virus invoca una chiamata a sistema operativo (tramite interrupt) che normalmente dovrebbe dare un certo esito, tranne che non ci sia una copia del virus in memoria, dato che in questo caso è il virus stesso che governa il pc e ne può variare il risultato!
Con questo semplice meccanismo un virus sa rivelare la sua presenza e se noi ne imitiamo le mosse, possiamo avere un antivirus che scansiona la memoria in un attimo: dai semplici calcoli 10.000 virus verrebbero scansionati in meno di 1 secondo, indipendentemente da quanta memoria sia installata sul sistema.
Oggigiorno la scansione di 4GB di Ram per trovare dei virus è una attività che richiede invece molto tempo, considerando poi che la velocità di lettura della memoria RAM è considerevolmente più veloce di quella dei dischi.
L’MVDL (Memory Virus Detect Language) era quindi il passo successivo: un linguaggio di programmazione per rilevare i virus in memoria, tramite un compilatore ed un interprete.
A quel tempo però non implementai il compilatore/interprete dell’MVDL, ma ne scrissi delle specifiche parziali (da cui posso fare degli esempi), mentre creai la versione in linguaggio macchina per testarne la funzionalità (cosa che avvenne con esito positivo)
Vediamo un esempio:
CODE 01; SEARCH FOR 'SLAYER'; ASSUME AX=0F1FAh; INT 21h; COMPARE AX WITH 0AAAAh BY = ON TRUE DO '%#^& in $' |
Il codice in MVDL qui sopra ricerca il virus Slayer in memoria; la corrispondente versione in assembler è la seguente:
MOV AX,0F1FAh INT 21h CMP AX,0AAAAh JE @VirusActiveInMemory |
Analizzando il codice assembler vediamo che:
- Il virus effettua una chiamata all’interrupt 21h (servizi del Dos) tramite l’istruzione INT 21h
- Alla routine viene passato un parametro all’interno del registro AX. Questo parametro non corrisponde a nessun servizio fornito dal sistema DOS.
- Dopo aver eseguito la routine associata al gestore di interrupt, l’istruzione eseguita è un confronto (CMP) in cui si verifica che ci sia il valore 0AAAAh nel registro AX
- In caso di esito positivo, il virus è attivo in memoria, dato che il DOS non avrebbe mai restituito quel risultato.
In termini del linguaggio MVDL, la prima istruzione è l’ancora di salvezza che permette di estendere il linguaggio a piacimento per stare al passo coi tempi (così come abbiamo visto per il linguaggio SVDL), mentre:
- SEARCH FOR è una dichiarazione che server per documentare per che virus abbiamo scritto questo codice di ricerca.
- ASSUME è una istruzione con cui possiamo assegnare ai registri del processore un valore
- INT è l’istruzione che richiama l’interrupt
- COMPARE è l’istruzione che confronta una variabile con un valore tramite l’operazione specificata da BY
- ON TRUE DO è l’istruzione che se il risultato del confronto è positivo, stampa le informazioni sull’avvenuta identificazione del virus in memoria tramite quello che è racchiuso negli ”.
L’output dell’MVDL (“SLAYER virus found active in memory”) si basa su queste macro:
- % stringa contenuta in SEARCH FOR ‘…’
- # virus
- $ memory
- @ MVDL
- & active
- ^ found
Vediamo un altro esempio che rintraccia il virus Flip (virus polimorfico):
CODE 01; SEARCH FOR 'FLIP'; ASSUME AX = 0FE01h; INT 21h; COMPARE AX WITH 01FEh BY =; ON TRUE DO '?' { Assembler instructions: MOV AX,0FE01h INT 21h CMP AX,01FEh JE virus_in_memory } |
Come possiamo vedere concettualmente non c’è nulla di differente rispetto al caso precedente, perchè il metodo è sempre lo stesso, cambiando solo i parametri di chiamata e il valore di confronto atteso.
Alcune note curiose: il virus Yankee Doddle utilizza una chiamata a INT 21 funzione C603 per verificare la sua presenza, ma è più sicuro utilizzare la chiamata a C600 (in quanto la precedente modifica anche dei registri in caso che il virus sia presente). Si potrebbe utilizzare anche la chiamata a C601 (che restituisce AH=0 ma è meno sicura), mentre sono poco utili le chiamate a C602 e C5xx.
Il virus Bebe non è un TSR, ma quando attiva il suo effetto “maligno”, copia il suo gestore dell’interrupt 1Ch nella memoria bassa (segmento 0000), e pone 2 byte prima della routine per sapere che c’è il suo gestore attivo. Pertanto questo virus è rintracciabile in memoria nel momento in cui è attivo.
Dato che non avevo fatto l’implementazione dell’MVDL visto che ne stavo ancora creando le specifiche, posto, per chi ha un background di programmazione, l’antivirus in versione nativa per fare la scansione della memoria.
L’antivirus rintraccia 21 virus ed è capace di identificare la famiglia a cui appartiene quel virus. Fa uso dell’oggetto MMV (Menager of Virus in Memory) in cui viene usato il codice in linguaggio assembler estratto dai virus per eseguire la scansione, e di un semplice file Pascal che richiama l’oggetto:
program Find_virus; uses Find_Virus; var Vir:MMV; begin Vir.Init(Find); If Vir.ValidStatus then if Vir.Allert then begin write('Found ',Vir.WhatVirus); if Vir.WhatFamily<>'none' then write(' (family: ',Vir.WhatFamily,')'); writeln(' in memory'); end else writeln('no virus in memory'); Vir.Done end. |
Ecco il codice della libreria:
unit find_virus; interface const NVirus = 21; NFamily = 2; {vn: Virus Name} vnNone = 0; vnDataLock = 1; vn1701 = 2; vnFlip = 3; vnJerusalem = 4; {jerusalem, jerusalem-A, 1244} vnSlayer = 5; vnTequila = 6; vnTopo = 7; vnDarkAvenger = 8; vn584 = 9; vn706_768 = 10; vn855_880 = 11; vnSunday = 12; vnKeyPressed = 13; vn1030 = 14; vnAlabama = 15; vn1554 = 16; {tenbyte} vnDoodle = 17; {Yankee Doodle} vnBadBoy = 18; vnDemolition = 19; vnCansu = 20; vnBebe = 21; {vf: Virus Family} vfNone = 0; vfJerusalem = 1; vf855 = 2; {ss: Status of Scan} ssOK = 0; ssScanOutRange = 1; const VirusFamily:Array[1..NFamily] of string[11]= ('Jerusalem', '855'); type VirusRec=record Name:string[16]; Family:byte; end; VirusTable=Array[1..NVirus] of VirusRec; const VirusInfo:VirusTable=( (Name: 'DataLock'; Family: vfNone), (Name: '1701'; Family: vfNone), (Name: 'Flip'; Family: vfNone), (Name: 'Jerusalem o 1244'; Family: vfJerusalem), (Name: 'Slayer'; Family: vfNone), (Name: 'Tequila'; Family: vfNone), (Name: 'Topo'; Family: vfNone), (Name: 'DarkAvenger'; Family: vfNone), (Name: '584'; Family: vf855), (Name: '706 o 768'; Family: vf855), (Name: '855 o 880'; Family: vf855), (Name: 'Sunday'; Family: vfJerusalem), (Name: 'KeyPressed'; Family: vfNone), (Name: '1030'; Family: vfNone), (Name: 'Alabama'; Family: vfNone), (Name: '1554'; Family: vfNone), (Name: 'Yankee Doodle'; Family: vfNone), (Name: 'BadBoy'; Family: vfNone), (Name: 'Demolition'; Family: vfNone), (Name: 'Cansu'; Family: vfNone), (Name: 'Bebe'; Family: vfNone)); {MMV:Menager of Virus in Memory} Type MMV=object ScanReport:byte; Status:byte; Constructor Init(Find:byte); Destructor Done; Procedure ReSearch; Virtual; Function Allert:Boolean; Virtual; Function WhatVirus:String; Virtual; Function WhatFamily:String; Virtual; Function ValidStatus:Boolean; Virtual; Function Version:word; Virtual; end; var Find:byte; implementation procedure Search; forward; Constructor MMV.Init(Find:byte); begin ScanReport:=Find; if ScanReport > NVirus then begin Status:=ssScanOutRange; ScanReport:=vnNone end else Status:=ssOK end; Destructor MMV.Done; begin end; Procedure MMV.ReSearch; begin Search; ScanReport:=Find; Status:=ssOk; end; Function MMV.Allert:Boolean; begin Allert:=ScanReport<>0; end; Function MMV.WhatVirus:String; begin if ScanReport=vnNone then WhatVirus:='No known Virus found in memory' else WhatVirus:=VirusInfo[ScanReport].Name; end; Function MMV.WhatFamily:String; begin if ScanReport=vnNone then WhatFamily:='' else if VirusInfo[ScanReport].Family=vfNone then WhatFamily:='none' else WhatFamily:=VirusFamily[VirusInfo[ScanReport].Family]; end; Function MMV.ValidStatus:boolean; begin ValidStatus:=Status=0; end; Function MMV.Version:word; begin Version:=NVirus; end; procedure search;assembler; const BabBoyTable:array[1..12] of byte= ($2e,$0ff,$36,$27,$01,$0E,$1F,$2E,$FF,$26,$25,$01); asm {DataLock} MOV Find,vnDataLock MOV AH,0BEh INT 21h CMP AX,1234h JZ @@virus_in_memory {1701} MOV Find,vn1701 MOV AX,4BFFh INT 21h CMP DI,55AAh JE @@virus_in_memory {Flip} MOV Find,vnFlip MOV AX,0FE01h INT 21h CMP AX,01FEh JE @@virus_in_memory {Jerusalem / 1244 / jerusalem-A} MOV Find,vnJerusalem MOV AH,0E0h INT 21h CMP AH,0E0h JAE @@not_jeru_in_mem CMP AH,03h JB @@not_jeru_in_mem JMP @@virus_in_memory @@not_jeru_in_mem: {Sunday} MOV Find,vnSunday MOV AH,0FFh INT 21h CMP AH,0FFh JAE @@not_Sun_in_mem CMP AH,04h JB @@not_Sun_in_mem JMP @@virus_in_memory @@not_sun_in_mem: {Slayer} MOV Find,vnSlayer MOV AX,0F1FAh INT 21h CMP AX,0AAAAh JE @@virus_in_memory {Tequila} MOV Find,vnTequila MOV AX,0FE02h INT 21h CMP AX,01FDh JE @@virus_in_memory {1030} MOV Find,vn1030 MOV AX,3521h INT 21h CMP BX,200h JNE @@not_1030_in_mem { MOV AX,3524h INT 21h CMP BX,484h JE @@virus_in_memory} JMP @@virus_in_memory @@not_1030_in_mem: {Yankee Doodle} MOV Find,vnDoodle MOV AX,0C600h CLC INT 21h JNC @@not_Doodle_in_mem CMP AX,002Ch JE @@Virus_in_memory @@not_Doodle_in_mem: {Demolition} MOV Find,vnDemolition MOV AX,4B7Fh INT 21h JNC @@Virus_in_memory {BadBoy} MOV Find,vnBadBoy MOV ES,PrefixSeg MOV ES,ES:[0002] MOV SI,Offset BabBoyTable MOV DI,100 MOV CX,000Bh REP CMPSB JZ @@Virus_in_memory {Bebe} MOV Find,vnBebe MOV AX,351Ch INT 21h MOV AX,ES:[BX-2] CMP AX,00FFh JNE @@Not_Bebe_in_mem PUSH ES POP AX OR AX,AX JZ @@Virus_in_memory @@Not_Bebe_In_mem: {Cansu} MOV Find,vnCansu PUSH DS XOR AX,AX MOV DS,AX MOV ES,DS:[004eh] MOV BX,00D6h CMP Word ptr ES:[BX],9876h JNE @@not_Cansu_in_mem POP DS JMP @@Virus_in_memory @@not_Cansu_in_mem: POP DS {Topo} MOV Find,vnTopo XOR BX,BX MOV ES,BX MOV AX,11h CMP ES:[3FEh],AX {0000:03fe} JE @@virus_in_memory {DarkAvenger} MOV Find,vnDarkAvenger PUSH DS {ES=0} LDS AX,ES:[0080h] CMP AX,2EEh JNE @@not_Dark_in_mem LDS AX,ES:[009Ch] CMP AX,2A9h JNE @@not_Dark_in_mem LDS AX,ES:[004Ch] CMP AX,6E4h JNE @@not_Dark_in_mem POP DS JMP @@virus_in_memory @@not_Dark_in_mem: POP DS {584 (della famiglia dell'855 -novembre 17-)} MOV Find,vn584 {ES=0} CMP ES:[020Ch].byte,4Dh JE @@virus_in_memory {855 e 880} MOV Find,vn855_880 {ES=0} CMP ES:[020Ch].word,5856h JE @@virus_in_memory {706 e 768 (della famiglia dell'855)} MOV Find,vn706_768 {ES=0} CMP ES:[020Ch].byte,56 JE @@virus_in_memory {KeyPressed} MOV Find,vnKeyPressed {ES=0} CMP ES:[0600h].byte,1 JE @@virus_in_memory MOV Find,vnAlabama {ES=0} MOV AX,ES:[0] OR AX,ES:[2] JZ @@virus_in_memory {1554} MOV Find,vn1554 {ES=0} CMP ES:[0084].word,0413h JNE @@not_1554_in_mem CMP ES:[0086].word,9A00h JE @@virus_in_memory @@not_1554_in_mem: MOV Find,vnNONE @@virus_in_memory: end; begin search; end. |
A questo punto un piccolo riassunto finale.
I concetti presenti in SVDL sulla ricerca algoritmica dei virus sono apparsi negli antivirus solo in alcuni casi proprio per l’identificazione dei virus polimorfici, anche se l’orientamento verso cui la ricerca si è spostata era quello della criptoanalisi per identificare i virus polimorfici (dato che le routine di mascheramento operano con poche istruzione, come XOR, alcune sequenze di codice anche crittografato del virus poteva lasciare una traccia comunque visibile sotto forma di pattern).
Riguardo a MVDL e alla scansione algoritmica della memoria, non mi è noto (ma potrei sbagliare) nessun prodotto che ne faccia o abbia fatto uso (e questo è un vero peccato, perchè vista l’estrema velocità di scansione, un antivirus potrebbe schedulare la scansione della memoria ogni tot minuti senza che questo intacchi le performance del computer).
23 Dicembre 2011 at 12:08
Stavo cercando un buon antivirus e ho scoperto Kaspersky con uno sconto di 10€ http://www.kaspersky.com/it/kaspersky_internet_security Poi ho scoperto che c’era anche Kaspersky Mobile Security in Regalo per il mio SmartPhone. Lo consiglio a tutti!