Píšeme polymorfický kód [I.]



Potřebujeme zabránit tomu, aby náš program (ať už dělá cokoli) byl těžko detekovatelný nějakým jiným softwarem. Jinak řečeno potřebujeme zajistit, aby nebylo snadné popsat náš spustitelný kód nějakou definicí, podle které by ho mohl nějaký čmuchal najít.

Článek se nezabývá pokročilými “hacky” na úrovni strojového kódu, ale čistě teorií pro obyčejné programátory.

1.) Polymorfický engine (loader)
Nejjednodušší cesta je to, že napíšeme program, který bude schopný náš kód zašifrovat a potom vždy rozšifrovat a spustit. To je fajn, ale má to pár háčků… třeba ten, že když někdo vytvoří definici zašifrovaného kódu, jsme tam, kde jsme byli (ve skutečnosti máme dvě verze naší binárky). Nic není ale ztraceno, existují postupy, kterými kód může tvořit poměrně velké množství svých variant sám o sobě…

Řekněme, že používáme nějakou jednoduchou šifru. Třeba XOR – není to pravá šifra, je to jen bitová operace a bylo by potřeba ji trochu okořenit, aby se pomocí ní dala bezpečně uchovat důvěrná data, ale pro naše účely nám postačí nejobyčejnější varianta šifrovacího algoritmu využívajícího XOR s 1 nebo 2 bytovým klíčem (tj. 256 nebo 65536 možných variant – kombinatoriku tu opravdu vysvětlovat nebudu) a pro zjednodušení budu mluvit právě o ní.

Jde o to, že náš kód byte po bytu nějak přibalíme do spustitelného souboru tohoto loaderu (zavaděče). Ten bude také obsahovat třeba 2 byty kterými ho vyXORujeme abychom ho rozkódovali. Nejdříve můžou být ty 2 byty klíče nastaveny na samé nuly. Když něco vyXORujeme nulami tak se to nezmění. Náš engine ale může při každém spuštění jako bonus vyXORovat oba byty klíče i celý kód dvěma náhodnými čísly (už je jedno, jestli tvoří svoji kopii, nebo upravuje sám sebe). Když vyXORujeme klíč stejnými byty jako náš kód, můžeme ho s ním opět pomocí XORu rozšifrovat (vycházím z předpokladu, že liché bity kódu se XORují jedním bytem klíče a sudé tím druhým…).

Podobně fungují programy na komprimaci spustitelných souborů (třeba gzexe nebo UPX). Tady se můžete podívat na to jak vypadá loader, který vytvoří gzexe (je napsaný v BASHi):

#!/bin/sh
skip=23
set -C
umask=`umask`
umask 77
tmpfile=`tempfile -p gztmp -d /tmp` || tmpfile=/tmp/gztmp.$$ || exit 1
if /usr/bin/tail -n +$skip "$0" | /bin/gzip -cd >> $tmpfile; then
umask $umask
/bin/chmod 700 $tmpfile
prog="`echo $0 | /bin/sed 's|^.*/||'`"
if /bin/ln $tmpfile "/tmp/$prog" 2>/dev/null; then
trap '/bin/rm -f $tmpfile "/tmp/$prog"; exit $res' 0
(/bin/sleep 5; /bin/rm -f $tmpfile "/tmp/$prog") 2>/dev/null &
/tmp/"$prog" ${1+"$@"}; res=$?
else
trap '/bin/rm -f $tmpfile; exit $res' 0
(/bin/sleep 5; /bin/rm -f $tmpfile) 2>/dev/null &
$tmpfile ${1+"$@"}; res=$?
fi
else
echo Cannot decompress $0; exit 1
fi; exit $res
#ZDE NA TÉTO ŘÁDCE BY UŽ BYL SPUSTITELNÝ KÓD ZKOMPRIMOVANÝ GZIPEM...

2.) Double trouble?
Ačkoli si každá antivirová (hoops kdo tu mluví o virech? ;) firma rozmyslí, jestli znečistí svoji databázi 65536 novými definicemi (kdyby to opravdu dělali, byla by to pro ně celkem konečná, protože by se ve svých definicích brzy utopili). Stejně nemyslytelné je pro ně, aby jejich software kontroloval každý soubor vícekrát v naději, že se mu podaří prolomit šifru (takový postup je nemyslytelný, protože by testování jednoho souboru mohlo trvat skoro jako test celého disku…). Také je ve skutečnosti situace taková, že proti polymorfním virům jsou AV společnosti poměrně bezmocné.

Malá vsuvka: (pokud vám vadí moje sebestřednost prosím přeskočte ;)Sám mám ze své Windowsové historie (není to tak dlouho co jsem na Linuxu) zkušenosti s napadením jedním velmi rozšířeným virem (záměrně nejmenuji), který se (mimo jiné) po napadení vysemeňoval (doslova) po celém disku. To vypadalo tak, že ve všech možných i nemožných adresářích se začínaly objevovat náhodně pojmenované soubory, proti kterým byly všechny možné antiviry bezmocné. Nakonec po několika scanech různými AV jsem se uchýlil ke studování toho worma. Byl celkem malý, ale já nechtěl studovat jeho kód, ale jeho chování. Po pár desítkách minut, kdy jsem ho zanalyzoval a našel pár chyb (nebudu jmenovat – budu rád, když je někdo udělá znova), které autor udělal se měl hotový scanner, který dokázal oscanovat disk, exáče najít a zlikvidovat. Sice jsem měl asi 3 false positive (neprávem označené) soubory, ale všechny ty soubory se trochu lišily už na první pohled, takže s tím jsem se taky popral. Když jsem měl jistotu, že mám ty správné soubory uvězněné v jednom adresáři, procesy pozabíjné a z registrů odstraněné klíče, které obsahovaly odkaz na ně, napsal jsem další skript, který udělal ze všech souborů MD5ky a zahodil ty které už měl. Hádejte kolik jich asi našel… Ano přesně 256, což dost dobře může svědčit o jednoduché jednobytové šifře. No a když jsem měl md5, tak jsem se rozhodl udělat další program, který spolu s několika dalšími testy tyto soubory dokázal už poznat bezpečně a pomocí toho jsem vyčistil další napadené počítače a dal ho na web.

Jenomže na co jsme nemysleli? Antivirové společnosti sice nemají reálnou šanci rozpoznat zašifrovaný kód, ale mohou detekovat loader, který ho rozšifruje a zavede. Potom už nemůžeme použít nějaký laciný trik jako je XOR… Potřebujeme aby kód byl spustitelný i ve “zmutované” formě. Prakticky je pár možností…

Pokud máme zdroják, můžeme si napsat skriptík, který bude veme mixér a pořádně náš kód promíchá. Fantazii se meze nekladou a skript může například nahrazovat jeden příkaz několika, které mají dohromady stejný význam. Nejjednodušším řešením je ale na každou řádku přidat ještě jednu, která bude obsahovat nic nedělající kód (to program zpomalí a ztěžkopádní), také je dobré vymyslet to tak, aby se takovým kódem prokládal pouze algoritmus, který by mohl být někým vyhodnocen jako nežádoucí nebo který by se nemohl vyskytovat v regulérním programu. Proto například všechny zavádějící řádky označíme například nějakým komentářem, který pak bude rozpoznán naším skriptem.

Představte si třeba následující kód v C:
puts("Ja jsem neskodny, nech me byt!");
for(int i=0;i!=100;i++) { //XXX
puts("Pozor na me!"); //XXX
} //XXX

Skript, který by všechny výskyty řetězce “//XXX” nahradil nějakým nic nedělajícím kódem je už jen otázkou minuty. Pod pojmem neškodný kód si představuji příkazy jako tyto:
sleep(0);
printf("");
time(0);
rand();
strcmp("","");

A další podle vašeho gusta/systému… Pokud jste opravdu bez fantazie, můžete si jako bonus přidělat funkce, které nic nedělají, nebo třeba sečtou několik čísel a vrátí výsledek, ty lze také volat poměrně beztrestně… Samozřejmě důležité je vybírat je buď náhodně, nebo podle pravidel permutace a případně je i kombinovat (víc na jednu řádku). Také můžete tyto mutace kompilovat s různě nastavenými optimalizacemi na straně kompilátoru. Stejně samozřejmě můžete modifikovat i interpretované jazyky, což je o to zajímavější, že tam ani nepotřebujete loader.

Teď si možná říkáte “proč děláme polymorfický loader, který musíme polymorfovat manuálně?”. Jde spíš o to, že tento engine není tak dlouhý (pár řádek), jako to co chceme šifrovat a proto ho vložený kód tolik nezpomalí a hlavně náš šifrovaný kód zůstane čistý a nemusíme od něj mít zdroják (ano velice zodpovědné jednání ;)

V praxi můžeme buď celou věc zautomatizovat (ale bylo by hloupé a hlavně nápadné neustále vše kompilovat znovu a znovu na napadeném stroji). Na druhé straně je možnost, že umíme Assembler a známe formát spustitelného souboru (ne já to neznám a neumím a tento článek se tím nezabývá) a pak můžeme napsat tak geniální kód, který dokáže nahrazovat kusy kódu na úrovni instrukcí za jejich alternativy, což je ale nesrovnatelně těžší a já se tím nezabývám.

Nemluvě o tom, že náš loader můžeme použít k obejití jednodušších antivirů, pokud chceme na nějakém stroji používat detekovaný program, který napsal někdo jiný (pravděpodobně chytřejší) a od kterého nemáme zdrojáky, pak nám ale stačí jednoduchá šifra bez polymorfismu. A stejně může náš loader do sebe uložit místo jednoho programu rovnou dva a tím nám vznikne exejoiner (který si ale necháme pro sebe, aby ho nikdo nedetekoval, stejně jako jsem to udělal s programy, které vám tu popisuji).

Nejjednodužší způsob je samozřejmě ten, který vám osvětlí dnešní
Motto dne: Nenechat se chytit

Protože když na sebe (nebo ještě hůře na vás) bude váš kód upozorňovat, tak někoho (koho za to platí) napadne ho zanalyzovat…

Aktualizace – 15.5. 2008: potom co jsem napsal tenhle článek (což ve mne obnovilo zájem o tuto problematiku) jsem si uvědomil, že člověk ve skutečnosti nemusí být zas takový borec přes asm (ve skutečnosti si nemyslím že budu potřebovat znát víc jak 5 instrukcí které jako inline ASM přidám do C kódu, abych dokázal napsat 100% polymorfický kód). Až budu mít někdy čas, tak zagooglím, ozkouším a předvedu svoje know-how (konkrétní kód samozřejmě ne). Můžete se těšit…




Líbí se vám článek? Chcete se o něj podělit? Přidejte ho! (volba topclanky.cz nevyžaduje registraci)

4 Responses to “Píšeme polymorfický kód [I.]”

  1. hgh Says:

    Prijde mi, ze na to jdes trochu naivne. Schovas kod, ale nechas loader. Antivir nemusi hledat ten kod, staci mu loader.

    Kdysi jsem psal antivir na virus, ktery mel dobre udelany polymorfujici loader. Ten kdo ho delal assembler znal a pouzival a loader byl pokazde jiny a presto se antivir napsat dal. To ze byl loader jiny neznamena jen dolneni nejake vaty do kodu ale primou modifikaci vykoneho kodu pomoci pouziti ruznych instrukci pro tu samou cinnost a pouzitim ruzneho kodovani.

    Pouzitim xor se ti v kodu objevi netradicni instrukce nedavajici smysl. Navic v mistech kam se predava rizeni. Myslim, ze toho si kazdy schopny antivirus musi vsimnout. Myslim, ze schopnemu antiviru neuniknes :)

  2. Harvie Says:

    hgh: precetl sis to cele?
    XOR je rychly, proto pouziju XORovaci loader…
    Samotny loader budu polymorfovat pomoci jineho toho kodu, co sem ted napsal, uz funguje a upravuje primo instrukce v ASM, ale je to samozrejme pomalejsi nez XOR…

  3. hgh Says:

    Precetl jsem to cele, nezpochybnuju, ze to funguje, ale rikam, ze
    1) antivir hleda chakteristickou sekvenci, kterou zna. Misto te sekvekvence, kterou schovas pres xor, bude hledat loader
    2) polymorfujici kod, ktery ma vykonat urcitou cinnost, je mozne stejne nakonec popsat a vyhledat s rozumnou mirou presnosti
    3) xorovanim kodu nebo netradicni kombinaci morfujicich instrukci v loaderu vznikaji podezrele muze byt podezrele konstrukce, ktere muze zachytit statisticka analyza antiviru.

  4. Harvie Says:

    hgh: no ja o tom vsem samozrejme vim.
    1) kdyz si to cetl cele, jiste sis vsiml ve clanku vety "Antivirové společnosti sice nemají reálnou šanci rozpoznat zašifrovaný kód, ale mohou detekovat loader"… Coz je pointou cele podkapitovy "Double trouble?".
    2) to asi nejlepe vystihuje veta "…když na sebe (nebo ještě hůře na vás) bude váš kód upozorňovat, tak někoho (koho za to platí) napadne ho zanalyzovat…"
    3) tim se budu zabyvat priste… v soucasne dobe mam sice neoptimalni ale funkcni engine. Chci do nej zabalit nejaky uz detekovany malware a poslat ho nekolika av k analyze, abych vyzkousel, jak se s nim poperou…. Kazdopadne dekuji za pripominku, ale takovou predstavivost mam take. Ja se snazim opirat o to, ze kdyby muj kod nekdo detekoval, zvysilo by mu to false-positives a proto se na me radeji vykasle… Kazdopadne kdo mel pravdu se dozvime, az AV skutecne poskadlim… ;)

Leave a Reply